<commit_before>use libc::{size_t, c_double, c_uchar, c_int};

use deflate::calculate_block_size_auto_type;
use lz77::{Lz77Store, ZopfliBlockState};
use util::{ZOPFLI_LARGE_FLOAT};
use zopfli::ZopfliOptions;

/// Finds minimum of function f(i) where is is of type size_t, f(i) is of type
/// double, i is in range start-end (excluding end).
/// Outputs the minimum value in *smallest and returns the index of this value.
pub fn find_minimum(f: fn(i: size_t, context: &SplitCostContext) -> c_double, context: &SplitCostContext, start: size_t, end: size_t) -> (size_t, c_double) {
    let mut start = start;
    let mut end = end;
    if end - start < 1024 {
        let mut best = ZOPFLI_LARGE_FLOAT;
        let mut result = start;
        for i in start..end {
            let v = f(i, context);
            if v < best {
                best = v;
                result = i;
            }
        }
        (result, best)
    } else {
        /* Try to find minimum faster by recursively checking multiple points. */
        let num = 9;  /* Good value: 9. ?!?!?!?! */
        let mut p = vec![0; num];
        let mut vp = vec![0.0; num];
        let mut besti;
        let mut best;
        let mut lastbest = ZOPFLI_LARGE_FLOAT;
        let mut pos = start;

        loop {
            if end - start <= num {
                break;
            }

            for i in 0..num {
                p[i] = start + (i + 1) * ((end - start) / (num + 1));
                vp[i] = f(p[i], context);
            }

            besti = 0;
            best = vp[0];

            for i in 1..num {
                if vp[i] < best {
                  best = vp[i];
                  besti = i;
                }
            }
            if best > lastbest {
                break;
            }

            start = if besti == 0 { start } else { p[besti - 1] };
            end = if besti == num - 1 { end } else { p[besti + 1] };

            pos = p[besti];
            lastbest = best;
        }
        (pos, lastbest)
    }
}

/// Returns estimated cost of a block in bits.  It includes the size to encode the
/// tree and the size to encode all literal, length and distance symbols and their
/// extra bits.
///
/// litlens: lz77 lit/lengths
/// dists: ll77 distances
/// lstart: start of block
/// lend: end of block (not inclusive)
pub fn estimate_cost(lz77: &Lz77Store, lstart: size_t, lend: size_t) -> c_double {
    calculate_block_size_auto_type(lz77, lstart, lend)
}

/// Gets the cost which is the sum of the cost of the left and the right section
/// of the data.
/// type: FindMinimumFun
pub fn split_cost(i: size_t, c: &SplitCostContext) -> c_double {
    estimate_cost(c.lz77, c.start, i) + estimate_cost(c.lz77, i, c.end)
}

pub struct SplitCostContext<'a> {
    lz77: &'a Lz77Store,
    start: size_t,
    end: size_t,
}

/// Finds next block to try to split, the largest of the available ones.
/// The largest is chosen to make sure that if only a limited amount of blocks is
/// requested, their sizes are spread evenly.
/// lz77size: the size of the LL77 data, which is the size of the done array here.
/// done: array indicating which blocks starting at that position are no longer
///     splittable (splitting them increases rather than decreases cost).
/// splitpoints: the splitpoints found so far.
/// npoints: the amount of splitpoints found so far.
/// lstart: output variable, giving start of block.
/// lend: output variable, giving end of block.
/// returns 1 if a block was found, 0 if no block found (all are done).
pub fn find_largest_splittable_block(lz77size: size_t, done: *const c_uchar, splitpoints: &Vec<size_t>, lstart: size_t, lend: size_t) -> (c_int, size_t, size_t) {
    let mut longest = 0;
    let mut found = 0;
    let mut lstart = lstart;
    let mut lend = lend;

    let npoints = splitpoints.len();

    for i in 0..(npoints + 1) {
        let start = if i == 0 { 0 } else { splitpoints[i - 1] };
        let end = if i == npoints { lz77size - 1 } else { splitpoints[i] };
        if unsafe { *done.offset(start as isize) } == 0 && end - start > longest {
            lstart = start;
            lend = end;
            found = 1;
            longest = end - start;
        }
    }

    (found, lstart, lend)
}

/// Prints the block split points as decimal and hex values in the terminal.
#[no_mangle]
#[allow(non_snake_case)]
pub extern fn print_block_split_points(lz77: &Lz77Store, lz77splitpoints: &Vec<size_t>) {
    let nlz77points = lz77splitpoints.len();
    let mut splitpoints = Vec::with_capacity(nlz77points);

    /* The input is given as lz77 indices, but we want to see the uncompressed
    index values. */
    let mut pos = 0;
    if nlz77points > 0 {
        for i in 0..lz77.size() {
            let length = if lz77.dists[i] == 0 {
                1
            } else {
                lz77.litlens[i]
            };
            if lz77splitpoints[splitpoints.len()] == i {
                splitpoints.push(pos);
                if splitpoints.len() == nlz77points {
                    break;
                }
            }
            pos += length;
        }
    }
    assert!(splitpoints.len() == nlz77points);

    println!("block split points: {} (hex: {})", splitpoints.iter().map(|&sp| format!("{}", sp)).collect::<Vec<_>>().join(" "), splitpoints.iter().map(|&sp| format!("{:x}", sp)).collect::<Vec<_>>().join(" "));
}

/// Does blocksplitting on LZ77 data.
/// The output splitpoints are indices in the LZ77 data.
/// maxblocks: set a limit to the amount of blocks. Set to 0 to mean no limit.
pub fn blocksplit_lz77(options: &ZopfliOptions, lz77: &Lz77Store, maxblocks: size_t, splitpoints: &mut Vec<size_t>) {

    if lz77.size() < 10 {
        return;  /* This code fails on tiny files. */
    }

    let mut llpos;
    let mut numblocks = 1;
    let mut splitcost: c_double;
    let mut origcost;
    let mut done = vec![0; lz77.size()];
    let mut lstart = 0;
    let mut lend = lz77.size();

    loop {
        if maxblocks > 0 && numblocks >= maxblocks {
          break;
        }
        let c = SplitCostContext {
            lz77: lz77,
            start: lstart,
            end: lend,
        };

        assert!(lstart < lend);
        let find_minimum_result = find_minimum(split_cost, &c, lstart + 1, lend);
        llpos = find_minimum_result.0;
        splitcost = find_minimum_result.1;

        assert!(llpos > lstart);
        assert!(llpos < lend);

        origcost = estimate_cost(lz77, lstart, lend);

        if splitcost > origcost || llpos == lstart + 1 || llpos == lend {
            done[lstart] = 1;
        } else {
            splitpoints.push(llpos);
            splitpoints.sort();
            numblocks += 1;
        }

        let find_block_results = find_largest_splittable_block(lz77.size(), done.as_ptr(), splitpoints, lstart, lend);
        lstart = find_block_results.1;
        lend = find_block_results.2;

        if find_block_results.0 == 0 {
            break;  /* No further split will probably reduce compression. */
        }

        if lend - lstart < 10 {
            break;
        }
    }

    if options.verbose > 0 {
        print_block_split_points(lz77, splitpoints);
    }
}

/// Does blocksplitting on uncompressed data.
/// The output splitpoints are indices in the uncompressed bytes.
///
/// options: general program options.
/// in: uncompressed input data
/// instart: where to start splitting
/// inend: where to end splitting (not inclusive)
/// maxblocks: maximum amount of blocks to split into, or 0 for no limit
/// splitpoints: dynamic array to put the resulting split point coordinates into.
///   The coordinates are indices in the input array.
/// npoints: pointer to amount of splitpoints, for the dynamic array. The amount of
///   blocks is the amount of splitpoitns + 1.
pub fn blocksplit(options: &ZopfliOptions, in_data: *const c_uchar, instart: size_t, inend: size_t, maxblocks: size_t, splitpoints: &mut Vec<size_t>) {
    let mut pos;
    let mut s = ZopfliBlockState::new(options, instart, inend, 0);

    let mut lz77splitpoints = Vec::with_capacity(maxblocks);

    let mut store = Lz77Store::new();

    splitpoints.clear();

    /* Unintuitively, Using a simple LZ77 method here instead of lz77_optimal
    results in better blocks. */
    store.greedy(&mut s, in_data, instart, inend);

    blocksplit_lz77(options, &store, maxblocks, &mut lz77splitpoints);

    let nlz77points = lz77splitpoints.len();

    /* Convert LZ77 positions to positions in the uncompressed input. */
    pos = instart;
    if nlz77points > 0 {
        for i in 0..store.size() {
            let length = if store.dists[i] == 0 { 1 } else { store.litlens[i] };
            if lz77splitpoints[splitpoints.len()] == i {
                splitpoints.push(pos);
                if splitpoints.len() == nlz77points {
                    break;
                }
            }
            pos += length as size_t;
        }
    }
    assert!(splitpoints.len() == nlz77points);
}
<commit_msg>Use an Option instead of an int<commit_after>use libc::{size_t, c_double, c_uchar};

use deflate::calculate_block_size_auto_type;
use lz77::{Lz77Store, ZopfliBlockState};
use util::{ZOPFLI_LARGE_FLOAT};
use zopfli::ZopfliOptions;

/// Finds minimum of function f(i) where is is of type size_t, f(i) is of type
/// double, i is in range start-end (excluding end).
/// Outputs the minimum value in *smallest and returns the index of this value.
pub fn find_minimum(f: fn(i: size_t, context: &SplitCostContext) -> c_double, context: &SplitCostContext, start: size_t, end: size_t) -> (size_t, c_double) {
    let mut start = start;
    let mut end = end;
    if end - start < 1024 {
        let mut best = ZOPFLI_LARGE_FLOAT;
        let mut result = start;
        for i in start..end {
            let v = f(i, context);
            if v < best {
                best = v;
                result = i;
            }
        }
        (result, best)
    } else {
        /* Try to find minimum faster by recursively checking multiple points. */
        let num = 9;  /* Good value: 9. ?!?!?!?! */
        let mut p = vec![0; num];
        let mut vp = vec![0.0; num];
        let mut besti;
        let mut best;
        let mut lastbest = ZOPFLI_LARGE_FLOAT;
        let mut pos = start;

        loop {
            if end - start <= num {
                break;
            }

            for i in 0..num {
                p[i] = start + (i + 1) * ((end - start) / (num + 1));
                vp[i] = f(p[i], context);
            }

            besti = 0;
            best = vp[0];

            for i in 1..num {
                if vp[i] < best {
                  best = vp[i];
                  besti = i;
                }
            }
            if best > lastbest {
                break;
            }

            start = if besti == 0 { start } else { p[besti - 1] };
            end = if besti == num - 1 { end } else { p[besti + 1] };

            pos = p[besti];
            lastbest = best;
        }
        (pos, lastbest)
    }
}

/// Returns estimated cost of a block in bits.  It includes the size to encode the
/// tree and the size to encode all literal, length and distance symbols and their
/// extra bits.
///
/// litlens: lz77 lit/lengths
/// dists: ll77 distances
/// lstart: start of block
/// lend: end of block (not inclusive)
pub fn estimate_cost(lz77: &Lz77Store, lstart: size_t, lend: size_t) -> c_double {
    calculate_block_size_auto_type(lz77, lstart, lend)
}

/// Gets the cost which is the sum of the cost of the left and the right section
/// of the data.
/// type: FindMinimumFun
pub fn split_cost(i: size_t, c: &SplitCostContext) -> c_double {
    estimate_cost(c.lz77, c.start, i) + estimate_cost(c.lz77, i, c.end)
}

pub struct SplitCostContext<'a> {
    lz77: &'a Lz77Store,
    start: size_t,
    end: size_t,
}

/// Finds next block to try to split, the largest of the available ones.
/// The largest is chosen to make sure that if only a limited amount of blocks is
/// requested, their sizes are spread evenly.
/// lz77size: the size of the LL77 data, which is the size of the done array here.
/// done: array indicating which blocks starting at that position are no longer
///     splittable (splitting them increases rather than decreases cost).
/// splitpoints: the splitpoints found so far.
/// npoints: the amount of splitpoints found so far.
/// lstart: output variable, giving start of block.
/// lend: output variable, giving end of block.
/// returns 1 if a block was found, 0 if no block found (all are done).
pub fn find_largest_splittable_block(lz77size: size_t, done: *const c_uchar, splitpoints: &Vec<size_t>) -> Option<(size_t, size_t)> {
    let mut longest = 0;
    let mut found = None;
    let npoints = splitpoints.len();

    for i in 0..(npoints + 1) {
        let start = if i == 0 { 0 } else { splitpoints[i - 1] };
        let end = if i == npoints { lz77size - 1 } else { splitpoints[i] };
        if unsafe { *done.offset(start as isize) } == 0 && end - start > longest {
            found = Some((start, end));
            longest = end - start;
        }
    }

    found
}

/// Prints the block split points as decimal and hex values in the terminal.
#[no_mangle]
#[allow(non_snake_case)]
pub extern fn print_block_split_points(lz77: &Lz77Store, lz77splitpoints: &Vec<size_t>) {
    let nlz77points = lz77splitpoints.len();
    let mut splitpoints = Vec::with_capacity(nlz77points);

    /* The input is given as lz77 indices, but we want to see the uncompressed
    index values. */
    let mut pos = 0;
    if nlz77points > 0 {
        for i in 0..lz77.size() {
            let length = if lz77.dists[i] == 0 {
                1
            } else {
                lz77.litlens[i]
            };
            if lz77splitpoints[splitpoints.len()] == i {
                splitpoints.push(pos);
                if splitpoints.len() == nlz77points {
                    break;
                }
            }
            pos += length;
        }
    }
    assert!(splitpoints.len() == nlz77points);

    println!("block split points: {} (hex: {})", splitpoints.iter().map(|&sp| format!("{}", sp)).collect::<Vec<_>>().join(" "), splitpoints.iter().map(|&sp| format!("{:x}", sp)).collect::<Vec<_>>().join(" "));
}

/// Does blocksplitting on LZ77 data.
/// The output splitpoints are indices in the LZ77 data.
/// maxblocks: set a limit to the amount of blocks. Set to 0 to mean no limit.
pub fn blocksplit_lz77(options: &ZopfliOptions, lz77: &Lz77Store, maxblocks: size_t, splitpoints: &mut Vec<size_t>) {

    if lz77.size() < 10 {
        return;  /* This code fails on tiny files. */
    }

    let mut llpos;
    let mut numblocks = 1;
    let mut splitcost: c_double;
    let mut origcost;
    let mut done = vec![0; lz77.size()];
    let mut lstart = 0;
    let mut lend = lz77.size();

    loop {
        if maxblocks > 0 && numblocks >= maxblocks {
          break;
        }
        let c = SplitCostContext {
            lz77: lz77,
            start: lstart,
            end: lend,
        };

        assert!(lstart < lend);
        let find_minimum_result = find_minimum(split_cost, &c, lstart + 1, lend);
        llpos = find_minimum_result.0;
        splitcost = find_minimum_result.1;

        assert!(llpos > lstart);
        assert!(llpos < lend);

        origcost = estimate_cost(lz77, lstart, lend);

        if splitcost > origcost || llpos == lstart + 1 || llpos == lend {
            done[lstart] = 1;
        } else {
            splitpoints.push(llpos);
            splitpoints.sort();
            numblocks += 1;
        }

        match find_largest_splittable_block(lz77.size(), done.as_ptr(), splitpoints) {
            None => break, /* No further split will probably reduce compression. */
            Some((start, end)) => {
                lstart = start;
                lend = end;
                if lend - lstart < 10 {
                    break;
                }
            }
        }
    }

    if options.verbose > 0 {
        print_block_split_points(lz77, splitpoints);
    }
}

/// Does blocksplitting on uncompressed data.
/// The output splitpoints are indices in the uncompressed bytes.
///
/// options: general program options.
/// in: uncompressed input data
/// instart: where to start splitting
/// inend: where to end splitting (not inclusive)
/// maxblocks: maximum amount of blocks to split into, or 0 for no limit
/// splitpoints: dynamic array to put the resulting split point coordinates into.
///   The coordinates are indices in the input array.
/// npoints: pointer to amount of splitpoints, for the dynamic array. The amount of
///   blocks is the amount of splitpoitns + 1.
pub fn blocksplit(options: &ZopfliOptions, in_data: *const c_uchar, instart: size_t, inend: size_t, maxblocks: size_t, splitpoints: &mut Vec<size_t>) {
    let mut pos;
    let mut s = ZopfliBlockState::new(options, instart, inend, 0);

    let mut lz77splitpoints = Vec::with_capacity(maxblocks);

    let mut store = Lz77Store::new();

    splitpoints.clear();

    /* Unintuitively, Using a simple LZ77 method here instead of lz77_optimal
    results in better blocks. */
    store.greedy(&mut s, in_data, instart, inend);

    blocksplit_lz77(options, &store, maxblocks, &mut lz77splitpoints);

    let nlz77points = lz77splitpoints.len();

    /* Convert LZ77 positions to positions in the uncompressed input. */
    pos = instart;
    if nlz77points > 0 {
        for i in 0..store.size() {
            let length = if store.dists[i] == 0 { 1 } else { store.litlens[i] };
            if lz77splitpoints[splitpoints.len()] == i {
                splitpoints.push(pos);
                if splitpoints.len() == nlz77points {
                    break;
                }
            }
            pos += length as size_t;
        }
    }
    assert!(splitpoints.len() == nlz77points);
}
<|endoftext|>


<commit_before>use std::os::unix::process::CommandExt;
use std::process::Command;

// clap exposes the macro value_t!
use clap::value_t;

// chrono needs this to call Utc.datetime_from_str().
use chrono::offset::TimeZone;

const SQLITE3: &'static str = "/usr/bin/sqlite3";
const DB_PATH: &'static str = "/home/kalvin/Documents/personal/expenditures.db";

const PEREXP: &'static str = "perexp";
const ESSEXP: &'static str = "essexp";

const CLAP_AMOUNT: &'static str = "amount";
const CLAP_DESCRIPTION: &'static str = "description";
const CLAP_EXPENDITURE_TYPE: &'static str = "expenditure-type";
const CLAP_TARGET_DATE: &'static str = "target-date";

#[derive(Debug)]
enum ExpenditureType {
    Personal,
    Essential,
}

#[derive(Debug)]
struct SqlOptions {
    // Expenditure type is always required in transacting expenditures.
    expenditure_type: ExpenditureType,

    // Target date is always required in transacting expenditures.
    target_date: chrono::Date<chrono::Utc>,

    // Amount and description are required for insertion.
    // They are meaningless for read-only queries.
    amount: Option<f64>,
    description: Option<String>,
}

#[derive(Debug)]
enum SqlCommand {
    Edit,
    Show(SqlOptions),
    Insert(SqlOptions),
}

fn expenditure_type_from_clap(matches: &clap::ArgMatches) -> ExpenditureType {
    let symbolic_type = matches.value_of(CLAP_EXPENDITURE_TYPE).unwrap();

    match symbolic_type {
        "p" => return ExpenditureType::Personal,
        "e" => return ExpenditureType::Essential,
        _ => (),
    };
    panic!(format!(
        "invalid {} ``{}''",
        CLAP_EXPENDITURE_TYPE, symbolic_type
    ));
}

fn expenditure_target_date_from_clap(
    matches: &clap::ArgMatches,
) -> Option<chrono::Date<chrono::Utc>> {
    if let Some(cli_target_date) = matches.value_of(CLAP_TARGET_DATE) {
        return Some(
            chrono::Utc
                .datetime_from_str(
                    &format!("{} 00:00:00", cli_target_date).to_string(),
                    "%Y-%m-%d %H:%M:%S",
                )
                .unwrap()
                .date(),
        );
    }
    None
}

fn expenditure_amount_from_clap(matches: &clap::ArgMatches) -> f64 {
    clap::value_t!(matches, CLAP_AMOUNT, f64).unwrap_or_else(|e| e.exit())
}

fn expenditure_description_from_clap(matches: &clap::ArgMatches) -> String {
    clap::value_t!(matches, CLAP_DESCRIPTION, String).unwrap_or_else(|e| e.exit())
}

fn build_show_options(matches: &clap::ArgMatches) -> SqlOptions {
    let target_date = match expenditure_target_date_from_clap(matches) {
        Some(date) => date,
        // Aribtrary choice: peeks back 6 months.
        None => (chrono::Utc::now() - chrono::Duration::days(183)).date(),
    };

    SqlOptions {
        expenditure_type: expenditure_type_from_clap(matches),
        target_date: target_date,
        amount: None,
        description: None,
    }
}

fn build_insert_options(matches: &clap::ArgMatches) -> SqlOptions {
    let target_date = match expenditure_target_date_from_clap(matches) {
        Some(date) => date,
        None => chrono::Utc::now().date(),
    };

    SqlOptions {
        expenditure_type: expenditure_type_from_clap(matches),
        target_date: target_date,
        amount: Some(expenditure_amount_from_clap(matches)),
        description: Some(expenditure_description_from_clap(matches)),
    }
}

fn parse_clap_matches(matches: clap::ArgMatches) -> SqlCommand {
    match matches.subcommand() {
        ("edit", _) => return SqlCommand::Edit,
        ("show", Some(show_matches)) => return SqlCommand::Show(build_show_options(show_matches)),
        ("insert", Some(insert_matches)) => {
            return SqlCommand::Insert(build_insert_options(insert_matches))
        }
        (&_, _) => panic!("no subcommand"),
    }
}

fn parse_command_line() -> SqlCommand {
    let matches = clap::App::new("tangerine-tailleferre")
        .version("0.1.0")
        .author("j39m")
        .about("manipulates expenditures")
        .subcommand(clap::App::new("edit").about("opens sqlite3 directly"))
        .subcommand(
            clap::App::new("show")
                .about("shows expenditures")
                .arg(
                    clap::Arg::with_name(CLAP_TARGET_DATE)
                        .takes_value(true)
                        .short("t"),
                )
                .arg(clap::Arg::with_name(CLAP_EXPENDITURE_TYPE).required(true)),
        )
        .subcommand(
            clap::App::new("insert")
                .about("inserts an expenditure")
                .arg(
                    clap::Arg::with_name(CLAP_AMOUNT)
                        .takes_value(true)
                        .required(true)
                        .short("a"),
                )
                .arg(
                    clap::Arg::with_name(CLAP_DESCRIPTION)
                        .takes_value(true)
                        .required(true)
                        .short("d"),
                )
                .arg(
                    clap::Arg::with_name(CLAP_TARGET_DATE)
                        .takes_value(true)
                        .short("t"),
                )
                .arg(clap::Arg::with_name(CLAP_EXPENDITURE_TYPE).required(true)),
        )
        .get_matches();

    parse_clap_matches(matches)
}

fn expenditure_type_name_from_enum(type_: &ExpenditureType) -> &'static str {
    match type_ {
        ExpenditureType::Personal => return PEREXP,
        ExpenditureType::Essential => return ESSEXP,
    }
}

fn build_show_command(options: SqlOptions) -> String {
    let wordy_display_command = format!(
        r#"select {} from {} where date >= date("{}");"#,
        "*",
        expenditure_type_name_from_enum(&options.expenditure_type),
        options.target_date.format("%Y-%m-%d").to_string()
    );
    let summation_command = format!(
        r#"select {} from {} where date >= date("{}");"#,
        "sum(amount)",
        expenditure_type_name_from_enum(&options.expenditure_type),
        options.target_date.format("%Y-%m-%d").to_string()
    );

    format!("{} {}", wordy_display_command, summation_command)
}

fn build_insert_command(options: SqlOptions) -> String {
    format!(
        r#"insert into {} values(date("{}"), "{}", {});"#,
        expenditure_type_name_from_enum(&options.expenditure_type),
        options.target_date.format("%Y-%m-%d"),
        options.description.unwrap(),
        options.amount.unwrap()
    )
}

fn build_extra_sqlite_arg() -> Option<String> {
    let sql_command = parse_command_line();

    match sql_command {
        SqlCommand::Edit => return None,
        SqlCommand::Show(options) => return Some(build_show_command(options)),
        SqlCommand::Insert(options) => return Some(build_insert_command(options)),
    };
}

fn main() {
    let mut command = Command::new(SQLITE3);
    command.arg(DB_PATH);

    if let Some(extra_arg) = build_extra_sqlite_arg() {
        command.arg(extra_arg);
    }

    eprintln!("{:#?}", command);
    command.exec();
}
<commit_msg>tangerine-tailleferre: use local time<commit_after>use std::os::unix::process::CommandExt;
use std::process::Command;

// clap exposes the macro value_t!
use clap::value_t;

// chrono needs this to call Local.datetime_from_str().
use chrono::offset::TimeZone;

const SQLITE3: &'static str = "/usr/bin/sqlite3";
const DB_PATH: &'static str = "/home/kalvin/Documents/personal/expenditures.db";

const PEREXP: &'static str = "perexp";
const ESSEXP: &'static str = "essexp";

const CLAP_AMOUNT: &'static str = "amount";
const CLAP_DESCRIPTION: &'static str = "description";
const CLAP_EXPENDITURE_TYPE: &'static str = "expenditure-type";
const CLAP_TARGET_DATE: &'static str = "target-date";

#[derive(Debug)]
enum ExpenditureType {
    Personal,
    Essential,
}

#[derive(Debug)]
struct SqlOptions {
    // Expenditure type is always required in transacting expenditures.
    expenditure_type: ExpenditureType,

    // Target date is always required in transacting expenditures.
    target_date: chrono::Date<chrono::Local>,

    // Amount and description are required for insertion.
    // They are meaningless for read-only queries.
    amount: Option<f64>,
    description: Option<String>,
}

#[derive(Debug)]
enum SqlCommand {
    Edit,
    Show(SqlOptions),
    Insert(SqlOptions),
}

fn expenditure_type_from_clap(matches: &clap::ArgMatches) -> ExpenditureType {
    let symbolic_type = matches.value_of(CLAP_EXPENDITURE_TYPE).unwrap();

    match symbolic_type {
        "p" => return ExpenditureType::Personal,
        "e" => return ExpenditureType::Essential,
        _ => (),
    };
    panic!(format!(
        "invalid {} ``{}''",
        CLAP_EXPENDITURE_TYPE, symbolic_type
    ));
}

fn expenditure_target_date_from_clap(
    matches: &clap::ArgMatches,
) -> Option<chrono::Date<chrono::Local>> {
    if let Some(cli_target_date) = matches.value_of(CLAP_TARGET_DATE) {
        return Some(
            chrono::Local
                .datetime_from_str(
                    &format!("{} 00:00:00", cli_target_date).to_string(),
                    "%Y-%m-%d %H:%M:%S",
                )
                .unwrap()
                .date(),
        );
    }
    None
}

fn expenditure_amount_from_clap(matches: &clap::ArgMatches) -> f64 {
    clap::value_t!(matches, CLAP_AMOUNT, f64).unwrap_or_else(|e| e.exit())
}

fn expenditure_description_from_clap(matches: &clap::ArgMatches) -> String {
    clap::value_t!(matches, CLAP_DESCRIPTION, String).unwrap_or_else(|e| e.exit())
}

fn build_show_options(matches: &clap::ArgMatches) -> SqlOptions {
    let target_date = match expenditure_target_date_from_clap(matches) {
        Some(date) => date,
        // Aribtrary choice: peeks back 6 months.
        None => (chrono::Local::now() - chrono::Duration::days(183)).date(),
    };

    SqlOptions {
        expenditure_type: expenditure_type_from_clap(matches),
        target_date: target_date,
        amount: None,
        description: None,
    }
}

fn build_insert_options(matches: &clap::ArgMatches) -> SqlOptions {
    let target_date = match expenditure_target_date_from_clap(matches) {
        Some(date) => date,
        None => chrono::Local::now().date(),
    };

    SqlOptions {
        expenditure_type: expenditure_type_from_clap(matches),
        target_date: target_date,
        amount: Some(expenditure_amount_from_clap(matches)),
        description: Some(expenditure_description_from_clap(matches)),
    }
}

fn parse_clap_matches(matches: clap::ArgMatches) -> SqlCommand {
    match matches.subcommand() {
        ("edit", _) => return SqlCommand::Edit,
        ("show", Some(show_matches)) => return SqlCommand::Show(build_show_options(show_matches)),
        ("insert", Some(insert_matches)) => {
            return SqlCommand::Insert(build_insert_options(insert_matches))
        }
        (&_, _) => panic!("no subcommand"),
    }
}

fn parse_command_line() -> SqlCommand {
    let matches = clap::App::new("tangerine-tailleferre")
        .version("0.1.0")
        .author("j39m")
        .about("manipulates expenditures")
        .subcommand(clap::App::new("edit").about("opens sqlite3 directly"))
        .subcommand(
            clap::App::new("show")
                .about("shows expenditures")
                .arg(
                    clap::Arg::with_name(CLAP_TARGET_DATE)
                        .takes_value(true)
                        .short("t"),
                )
                .arg(clap::Arg::with_name(CLAP_EXPENDITURE_TYPE).required(true)),
        )
        .subcommand(
            clap::App::new("insert")
                .about("inserts an expenditure")
                .arg(
                    clap::Arg::with_name(CLAP_AMOUNT)
                        .takes_value(true)
                        .required(true)
                        .short("a"),
                )
                .arg(
                    clap::Arg::with_name(CLAP_DESCRIPTION)
                        .takes_value(true)
                        .required(true)
                        .short("d"),
                )
                .arg(
                    clap::Arg::with_name(CLAP_TARGET_DATE)
                        .takes_value(true)
                        .short("t"),
                )
                .arg(clap::Arg::with_name(CLAP_EXPENDITURE_TYPE).required(true)),
        )
        .get_matches();

    parse_clap_matches(matches)
}

fn expenditure_type_name_from_enum(type_: &ExpenditureType) -> &'static str {
    match type_ {
        ExpenditureType::Personal => return PEREXP,
        ExpenditureType::Essential => return ESSEXP,
    }
}

fn build_show_command(options: SqlOptions) -> String {
    let wordy_display_command = format!(
        r#"select {} from {} where date >= date("{}");"#,
        "*",
        expenditure_type_name_from_enum(&options.expenditure_type),
        options.target_date.format("%Y-%m-%d").to_string()
    );
    let summation_command = format!(
        r#"select {} from {} where date >= date("{}");"#,
        "sum(amount)",
        expenditure_type_name_from_enum(&options.expenditure_type),
        options.target_date.format("%Y-%m-%d").to_string()
    );

    format!("{} {}", wordy_display_command, summation_command)
}

fn build_insert_command(options: SqlOptions) -> String {
    format!(
        r#"insert into {} values(date("{}"), "{}", {});"#,
        expenditure_type_name_from_enum(&options.expenditure_type),
        options.target_date.format("%Y-%m-%d"),
        options.description.unwrap(),
        options.amount.unwrap()
    )
}

fn build_extra_sqlite_arg() -> Option<String> {
    let sql_command = parse_command_line();

    match sql_command {
        SqlCommand::Edit => return None,
        SqlCommand::Show(options) => return Some(build_show_command(options)),
        SqlCommand::Insert(options) => return Some(build_insert_command(options)),
    };
}

fn main() {
    let mut command = Command::new(SQLITE3);
    command.arg(DB_PATH);

    if let Some(extra_arg) = build_extra_sqlite_arg() {
        command.arg(extra_arg);
    }

    eprintln!("{:#?}", command);
    command.exec();
}
<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

//! Two simple cache data structures.

use std::collections::hash_map::RandomState;
use std::hash::{Hash, Hasher, BuildHasher};
use std::slice::{Iter, IterMut};

pub struct LRUCache<K, V> {
    entries: Vec<(K, V)>,
    cache_size: usize,
}

impl<K: PartialEq, V: Clone> LRUCache<K, V> {
    pub fn new(size: usize) -> LRUCache<K, V> {
        LRUCache {
          entries: vec![],
          cache_size: size,
        }
    }

    #[inline]
    pub fn touch(&mut self, pos: usize) -> &V {
        let last_index = self.entries.len() - 1;
        if pos != last_index {
            let entry = self.entries.remove(pos);
            self.entries.push(entry);
        }
        &self.entries[last_index].1
    }

    pub fn iter(&self) -> Iter<(K, V)> {
        self.entries.iter()
    }

    pub fn iter_mut(&mut self) -> IterMut<(K, V)> {
        self.entries.iter_mut()
    }

    pub fn insert(&mut self, key: K, val: V) {
        if self.entries.len() == self.cache_size {
            self.entries.remove(0);
        }
        self.entries.push((key, val));
    }

    pub fn find(&mut self, key: &K) -> Option<V> {
        match self.entries.iter().position(|&(ref k, _)| key == k) {
            Some(pos) => Some(self.touch(pos).clone()),
            None      => None,
        }
    }

    pub fn find_or_create<F>(&mut self, key: K, mut blk: F) -> V where F: FnMut() -> V {
        match self.entries.iter().position(|&(ref k, _)| *k == key) {
            Some(pos) => self.touch(pos).clone(),
            None => {
                let val = blk();
                self.insert(key, val.clone());
                val
            }
        }
    }

    pub fn evict_all(&mut self) {
        self.entries.clear();
    }
}

pub struct SimpleHashCache<K, V> {
    entries: Vec<Option<(K, V)>>,
    random: RandomState,
}

impl<K: Clone + Eq + Hash, V: Clone> SimpleHashCache<K, V> {
    pub fn new(cache_size: usize) -> SimpleHashCache<K, V> {
        SimpleHashCache {
            entries: vec![None; cache_size],
            random: RandomState::new(),
        }
    }

    #[inline]
    fn to_bucket(&self, h: usize) -> usize {
        h % self.entries.len()
    }

    #[inline]
    fn bucket_for_key<Q: Hash>(&self, key: &Q) -> usize {
        let mut hasher = self.random.build_hasher();
        key.hash(&mut hasher);
        self.to_bucket(hasher.finish() as usize)
    }

    pub fn insert(&mut self, key: K, value: V) {
        let bucket_index = self.bucket_for_key(&key);
        self.entries[bucket_index] = Some((key, value));
    }

    pub fn find<Q>(&self, key: &Q) -> Option<V> where Q: PartialEq<K> + Hash + Eq {
        let bucket_index = self.bucket_for_key(key);
        match self.entries[bucket_index] {
            Some((ref existing_key, ref value)) if key == existing_key => Some((*value).clone()),
            _ => None,
        }
    }

    pub fn find_or_create<F>(&mut self, key: K, mut blk: F) -> V where F: FnMut() -> V {
        if let Some(value) = self.find(&key) {
            return value;
        }
        let value = blk();
        self.insert(key, value.clone());
        value
    }

    pub fn evict_all(&mut self) {
        for slot in &mut self.entries {
            *slot = None
        }
    }
}
<commit_msg>style: Document and remove dead code from cache.rs<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

//! A simple LRU cache.

#![deny(missing_docs)]

use std::slice::{Iter, IterMut};

/// A LRU cache used to store a set of at most `n` elements at the same time.
///
/// Currently used for the style sharing candidate cache.
pub struct LRUCache<K, V> {
    entries: Vec<(K, V)>,
    cache_size: usize,
}

impl<K: PartialEq, V: Clone> LRUCache<K, V> {
    /// Create a new LRU cache with `size` elements at most.
    pub fn new(size: usize) -> LRUCache<K, V> {
        LRUCache {
          entries: vec![],
          cache_size: size,
        }
    }

    #[inline]
    /// Touch a given position, and put it in the last item on the list.
    pub fn touch(&mut self, pos: usize) -> &V {
        let last_index = self.entries.len() - 1;
        if pos != last_index {
            let entry = self.entries.remove(pos);
            self.entries.push(entry);
        }
        &self.entries[last_index].1
    }

    /// Iterate over the contents of this cache.
    pub fn iter(&self) -> Iter<(K, V)> {
        self.entries.iter()
    }

    /// Iterate mutably over the contents of this cache.
    pub fn iter_mut(&mut self) -> IterMut<(K, V)> {
        self.entries.iter_mut()
    }

    /// Insert a given key and value in the cache.
    pub fn insert(&mut self, key: K, val: V) {
        if self.entries.len() == self.cache_size {
            self.entries.remove(0);
        }
        self.entries.push((key, val));
    }

    /// Try to find a key in the cache.
    pub fn find(&mut self, key: &K) -> Option<V> {
        match self.entries.iter().position(|&(ref k, _)| key == k) {
            Some(pos) => Some(self.touch(pos).clone()),
            None      => None,
        }
    }

    /// Try to find a given key, or create a given item with that key executing
    /// `blk`.
    pub fn find_or_create<F>(&mut self, key: K, mut blk: F) -> V
        where F: FnMut() -> V,
    {
        match self.entries.iter().position(|&(ref k, _)| *k == key) {
            Some(pos) => self.touch(pos).clone(),
            None => {
                let val = blk();
                self.insert(key, val.clone());
                val
            }
        }
    }

    /// Evict all elements from the cache.
    pub fn evict_all(&mut self) {
        self.entries.clear();
    }
}
<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

//! A simple LRU cache.

#![deny(missing_docs)]

extern crate arraydeque;
use self::arraydeque::Array;
use self::arraydeque::ArrayDeque;

/// A LRU cache used to store a set of at most `n` elements at the same time.
///
/// The most-recently-used entry is at index zero.
pub struct LRUCache <K: Array>{
    entries: ArrayDeque<K>,
}

/// A iterator over the items of the LRU cache.
pub type LRUCacheIterator<'a, K> = arraydeque::Iter<'a, K>;

/// A iterator over the mutable items of the LRU cache.
pub type LRUCacheMutIterator<'a, K> = arraydeque::IterMut<'a, K>;

impl<K: Array> LRUCache<K> {
    /// Create a new LRU cache with `size` elements at most.
    pub fn new() -> Self {
        LRUCache {
          entries: ArrayDeque::new(),
        }
    }

    /// Returns the number of elements in the cache.
    pub fn num_entries(&self) -> usize {
        self.entries.len()
    }

    #[inline]
    /// Touch a given entry, putting it first in the list.
    pub fn touch(&mut self, pos: usize) {
        let last_index = self.entries.len() - 1;
        if pos != last_index {
            let entry = self.entries.remove(pos).unwrap();
            self.entries.push_front(entry);
        }
    }

    /// Iterate over the contents of this cache, from more to less recently
    /// used.
    pub fn iter(&self) -> arraydeque::Iter<K::Item> {
        self.entries.iter()
    }

    /// Iterate mutably over the contents of this cache.
    pub fn iter_mut(&mut self) -> arraydeque::IterMut<K::Item> {
        self.entries.iter_mut()
    }

    /// Insert a given key in the cache.
    pub fn insert(&mut self, key: K::Item) {
        if self.entries.len() == self.entries.capacity() {
            self.entries.pop_back();
        }
        self.entries.push_front(key);
        debug_assert!(self.entries.len() <= self.entries.capacity());
    }

    /// Evict all elements from the cache.
    pub fn evict_all(&mut self) {
        self.entries.clear();
    }
}
<commit_msg>Fix an awful bug in LRUCache::touch().<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

//! A simple LRU cache.

#![deny(missing_docs)]

extern crate arraydeque;
use self::arraydeque::Array;
use self::arraydeque::ArrayDeque;

/// A LRU cache used to store a set of at most `n` elements at the same time.
///
/// The most-recently-used entry is at index zero.
pub struct LRUCache <K: Array>{
    entries: ArrayDeque<K>,
}

/// A iterator over the items of the LRU cache.
pub type LRUCacheIterator<'a, K> = arraydeque::Iter<'a, K>;

/// A iterator over the mutable items of the LRU cache.
pub type LRUCacheMutIterator<'a, K> = arraydeque::IterMut<'a, K>;

impl<K: Array> LRUCache<K> {
    /// Create a new LRU cache with `size` elements at most.
    pub fn new() -> Self {
        LRUCache {
          entries: ArrayDeque::new(),
        }
    }

    /// Returns the number of elements in the cache.
    pub fn num_entries(&self) -> usize {
        self.entries.len()
    }

    #[inline]
    /// Touch a given entry, putting it first in the list.
    pub fn touch(&mut self, pos: usize) {
        if pos != 0 {
            let entry = self.entries.remove(pos).unwrap();
            self.entries.push_front(entry);
        }
    }

    /// Iterate over the contents of this cache, from more to less recently
    /// used.
    pub fn iter(&self) -> arraydeque::Iter<K::Item> {
        self.entries.iter()
    }

    /// Iterate mutably over the contents of this cache.
    pub fn iter_mut(&mut self) -> arraydeque::IterMut<K::Item> {
        self.entries.iter_mut()
    }

    /// Insert a given key in the cache.
    pub fn insert(&mut self, key: K::Item) {
        if self.entries.len() == self.entries.capacity() {
            self.entries.pop_back();
        }
        self.entries.push_front(key);
        debug_assert!(self.entries.len() <= self.entries.capacity());
    }

    /// Evict all elements from the cache.
    pub fn evict_all(&mut self) {
        self.entries.clear();
    }
}
<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

//! Memory profiling functions.

use libc::{c_char,c_int,c_void,size_t};
use std::borrow::ToOwned;
use std::ffi::CString;
use std::old_io::timer::sleep;
#[cfg(target_os="linux")]
use std::old_io::File;
use std::mem::size_of;
use std::ptr::null_mut;
use std::sync::mpsc::{Sender, channel, Receiver};
use std::time::duration::Duration;
use task::spawn_named;
#[cfg(target_os="macos")]
use task_info::task_basic_info::{virtual_size,resident_size};

pub struct MemoryProfilerChan(pub Sender<MemoryProfilerMsg>);

impl MemoryProfilerChan {
    pub fn send(&self, msg: MemoryProfilerMsg) {
        let MemoryProfilerChan(ref c) = *self;
        c.send(msg).unwrap();
    }
}

pub enum MemoryProfilerMsg {
    /// Message used to force print the memory profiling metrics.
    Print,
    /// Tells the memory profiler to shut down.
    Exit,
}

pub struct MemoryProfiler {
    pub port: Receiver<MemoryProfilerMsg>,
}

impl MemoryProfiler {
    pub fn create(period: Option<f64>) -> MemoryProfilerChan {
        let (chan, port) = channel();
        match period {
            Some(period) => {
                let period = Duration::milliseconds((period * 1000f64) as i64);
                let chan = chan.clone();
                spawn_named("Memory profiler timer".to_owned(), move || {
                    loop {
                        sleep(period);
                        if chan.send(MemoryProfilerMsg::Print).is_err() {
                            break;
                        }
                    }
                });
                // Spawn the memory profiler.
                spawn_named("Memory profiler".to_owned(), move || {
                    let memory_profiler = MemoryProfiler::new(port);
                    memory_profiler.start();
                });
            }
            None => {
                // No-op to handle messages when the memory profiler is
                // inactive.
                spawn_named("Memory profiler".to_owned(), move || {
                    loop {
                        match port.recv() {
                            Err(_) | Ok(MemoryProfilerMsg::Exit) => break,
                            _ => {}
                        }
                    }
                });
            }
        }

        MemoryProfilerChan(chan)
    }

    pub fn new(port: Receiver<MemoryProfilerMsg>) -> MemoryProfiler {
        MemoryProfiler {
            port: port
        }
    }

    pub fn start(&self) {
        loop {
            match self.port.recv() {
               Ok(msg) => {
                   if !self.handle_msg(msg) {
                       break
                   }
               }
               _ => break
            }
        }
    }

    fn handle_msg(&self, msg: MemoryProfilerMsg) -> bool {
        match msg {
            MemoryProfilerMsg::Print => {
                self.handle_print_msg();
                true
            },
            MemoryProfilerMsg::Exit => false
        }
    }

    fn print_measurement(path: &str, nbytes: Option<u64>) {
        match nbytes {
            Some(nbytes) => {
                let mebi = 1024f64 * 1024f64;
                println!("{:12.2}: {}", (nbytes as f64) / mebi, path);
            }
            None => {
                println!("{:>12}: {}", "???", path);
            }
        }
    }

    fn handle_print_msg(&self) {
        println!("{:12}: {}", "_size (MiB)_", "_category_");

        // Virtual and physical memory usage, as reported by the OS.
        MemoryProfiler::print_measurement("vsize", get_vsize());
        MemoryProfiler::print_measurement("resident", get_resident());

        for seg in get_resident_segments().iter() {
            MemoryProfiler::print_measurement(seg.0.as_slice(), Some(seg.1));
        }

        // Total number of bytes allocated by the application on the system
        // heap.
        MemoryProfiler::print_measurement("system-heap-allocated",
                                          get_system_heap_allocated());

        // The descriptions of the following jemalloc measurements are taken
        // directly from the jemalloc documentation.

        // "Total number of bytes allocated by the application."
        MemoryProfiler::print_measurement("jemalloc-heap-allocated",
                                          get_jemalloc_stat("stats.allocated"));

        // "Total number of bytes in active pages allocated by the application.
        // This is a multiple of the page size, and greater than or equal to
        // |stats.allocated|."
        MemoryProfiler::print_measurement("jemalloc-heap-active",
                                          get_jemalloc_stat("stats.active"));

        // "Total number of bytes in chunks mapped on behalf of the application.
        // This is a multiple of the chunk size, and is at least as large as
        // |stats.active|. This does not include inactive chunks."
        MemoryProfiler::print_measurement("jemalloc-heap-mapped",
                                          get_jemalloc_stat("stats.mapped"));

        println!("");
    }
}

#[cfg(target_os="linux")]
extern {
    fn mallinfo() -> struct_mallinfo;
}

#[cfg(target_os="linux")]
#[repr(C)]
pub struct struct_mallinfo {
    arena:    c_int,
    ordblks:  c_int,
    smblks:   c_int,
    hblks:    c_int,
    hblkhd:   c_int,
    usmblks:  c_int,
    fsmblks:  c_int,
    uordblks: c_int,
    fordblks: c_int,
    keepcost: c_int,
}

#[cfg(target_os="linux")]
fn get_system_heap_allocated() -> Option<u64> {
    let mut info: struct_mallinfo;
    unsafe {
        info = mallinfo();
    }
    // The documentation in the glibc man page makes it sound like |uordblks|
    // would suffice, but that only gets the small allocations that are put in
    // the brk heap. We need |hblkhd| as well to get the larger allocations
    // that are mmapped.
    Some((info.hblkhd + info.uordblks) as u64)
}

#[cfg(not(target_os="linux"))]
fn get_system_heap_allocated() -> Option<u64> {
    None
}

extern {
    fn je_mallctl(name: *const c_char, oldp: *mut c_void, oldlenp: *mut size_t,
                  newp: *mut c_void, newlen: size_t) -> c_int;
}

fn get_jemalloc_stat(value_name: &str) -> Option<u64> {
    // Before we request the measurement of interest, we first send an "epoch"
    // request. Without that jemalloc gives cached statistics(!) which can be
    // highly inaccurate.
    let epoch_name = "epoch";
    let epoch_c_name = CString::from_slice(epoch_name.as_bytes());
    let mut epoch: u64 = 0;
    let epoch_ptr = &mut epoch as *mut _ as *mut c_void;
    let mut epoch_len = size_of::<u64>() as size_t;

    let value_c_name = CString::from_slice(value_name.as_bytes());
    let mut value: size_t = 0;
    let value_ptr = &mut value as *mut _ as *mut c_void;
    let mut value_len = size_of::<size_t>() as size_t;

    // Using the same values for the `old` and `new` parameters is enough
    // to get the statistics updated.
    let rv = unsafe {
        je_mallctl(epoch_c_name.as_ptr(), epoch_ptr, &mut epoch_len, epoch_ptr,
                   epoch_len)
    };
    if rv != 0 {
        return None;
    }

    let rv = unsafe {
        je_mallctl(value_c_name.as_ptr(), value_ptr, &mut value_len,
                   null_mut(), 0)
    };
    if rv != 0 {
        return None;
    }

    Some(value as u64)
}

// Like std::macros::try!, but for Option<>.
macro_rules! option_try(
    ($e:expr) => (match $e { Some(e) => e, None => return None })
);

#[cfg(target_os="linux")]
fn get_proc_self_statm_field(field: usize) -> Option<u64> {
    let mut f = File::open(&Path::new("/proc/self/statm"));
    match f.read_to_string() {
        Ok(contents) => {
            let s = option_try!(contents.as_slice().words().nth(field));
            let npages = option_try!(s.parse::<u64>().ok());
            Some(npages * (::std::env::page_size() as u64))
        }
        Err(_) => None
    }
}

#[cfg(target_os="linux")]
fn get_vsize() -> Option<u64> {
    get_proc_self_statm_field(0)
}

#[cfg(target_os="linux")]
fn get_resident() -> Option<u64> {
    get_proc_self_statm_field(1)
}

#[cfg(target_os="macos")]
fn get_vsize() -> Option<u64> {
    virtual_size()
}

#[cfg(target_os="macos")]
fn get_resident() -> Option<u64> {
    resident_size()
}

#[cfg(not(any(target_os="linux", target_os = "macos")))]
fn get_vsize() -> Option<u64> {
    None
}

#[cfg(not(any(target_os="linux", target_os = "macos")))]
fn get_resident() -> Option<u64> {
    None
}

#[cfg(target_os="linux")]
fn get_resident_segments() -> Vec<(String, u64)> {
    use regex::Regex;
    use std::collections::HashMap;
    use std::collections::hash_map::Entry;

    // The first line of an entry in /proc/<pid>/smaps looks just like an entry
    // in /proc/<pid>/maps:
    //
    //   address           perms offset  dev   inode  pathname
    //   02366000-025d8000 rw-p 00000000 00:00 0      [heap]
    //
    // Each of the following lines contains a key and a value, separated
    // by ": ", where the key does not contain either of those characters.
    // For example:
    //
    //   Rss:           132 kB

    let path = Path::new("/proc/self/smaps");
    let mut f = ::std::old_io::BufferedReader::new(File::open(&path));

    let seg_re = Regex::new(
        r"^[:xdigit:]+-[:xdigit:]+ (....) [:xdigit:]+ [:xdigit:]+:[:xdigit:]+ \d+ +(.*)").unwrap();
    let rss_re = Regex::new(r"^Rss: +(\d+) kB").unwrap();

    // We record each segment's resident size.
    let mut seg_map: HashMap<String, u64> = HashMap::new();

    #[derive(PartialEq)]
    enum LookingFor { Segment, Rss }
    let mut looking_for = LookingFor::Segment;

    let mut curr_seg_name = String::new();

    // Parse the file.
    for line in f.lines() {
        let line = match line {
            Ok(line) => line,
            Err(_) => continue,
        };
        if looking_for == LookingFor::Segment {
            // Look for a segment info line.
            let cap = match seg_re.captures(line.as_slice()) {
                Some(cap) => cap,
                None => continue,
            };
            let perms = cap.at(1).unwrap();
            let pathname = cap.at(2).unwrap();

            // Construct the segment name from its pathname and permissions.
            curr_seg_name.clear();
            curr_seg_name.push_str("- ");
            if pathname == "" || pathname.starts_with("[stack:") {
                // Anonymous memory. Entries marked with "[stack:nnn]"
                // look like thread stacks but they may include other
                // anonymous mappings, so we can't trust them and just
                // treat them as entirely anonymous.
                curr_seg_name.push_str("anonymous");
            } else {
                curr_seg_name.push_str(pathname);
            }
            curr_seg_name.push_str(" (");
            curr_seg_name.push_str(perms);
            curr_seg_name.push_str(")");

            looking_for = LookingFor::Rss;
        } else {
            // Look for an "Rss:" line.
            let cap = match rss_re.captures(line.as_slice()) {
                Some(cap) => cap,
                None => continue,
            };
            let rss = cap.at(1).unwrap().parse::<u64>().unwrap() * 1024;

            if rss > 0 {
                // Aggregate small segments into "- other".
                let seg_name = if rss < 512 * 1024 {
                    "- other".to_owned()
                } else {
                    curr_seg_name.clone()
                };
                match seg_map.entry(seg_name) {
                    Entry::Vacant(entry) => { entry.insert(rss); },
                    Entry::Occupied(mut entry) => *entry.get_mut() += rss,
                }
            }

            looking_for = LookingFor::Segment;
        }
    }

    let mut segs: Vec<(String, u64)> = seg_map.into_iter().collect();

    // Get the total and add it to the vector. Note that this total differs
    // from the "resident" measurement obtained via /proc/<pid>/statm in
    // get_resident(). It's unclear why this difference occurs; for some
    // processes the measurements match, but for Servo they do not.
    let total = segs.iter().fold(0u64, |total, &(_, size)| total + size);
    segs.push(("resident-according-to-smaps".to_owned(), total));

    // Sort by size; the total will be first.
    segs.sort_by(|&(_, rss1), &(_, rss2)| rss2.cmp(&rss1));

    segs
}

#[cfg(not(target_os="linux"))]
fn get_resident_segments() -> Vec<(String, u64)> {
    vec![]
}

<commit_msg>auto merge of #5114 : psdh/servo/foldsum, r=jdm<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

//! Memory profiling functions.

use libc::{c_char,c_int,c_void,size_t};
use std::borrow::ToOwned;
use std::ffi::CString;
use std::iter::AdditiveIterator;
use std::old_io::timer::sleep;
#[cfg(target_os="linux")]
use std::old_io::File;
use std::mem::size_of;
use std::ptr::null_mut;
use std::sync::mpsc::{Sender, channel, Receiver};
use std::time::duration::Duration;
use task::spawn_named;
#[cfg(target_os="macos")]
use task_info::task_basic_info::{virtual_size,resident_size};

pub struct MemoryProfilerChan(pub Sender<MemoryProfilerMsg>);

impl MemoryProfilerChan {
    pub fn send(&self, msg: MemoryProfilerMsg) {
        let MemoryProfilerChan(ref c) = *self;
        c.send(msg).unwrap();
    }
}

pub enum MemoryProfilerMsg {
    /// Message used to force print the memory profiling metrics.
    Print,
    /// Tells the memory profiler to shut down.
    Exit,
}

pub struct MemoryProfiler {
    pub port: Receiver<MemoryProfilerMsg>,
}

impl MemoryProfiler {
    pub fn create(period: Option<f64>) -> MemoryProfilerChan {
        let (chan, port) = channel();
        match period {
            Some(period) => {
                let period = Duration::milliseconds((period * 1000f64) as i64);
                let chan = chan.clone();
                spawn_named("Memory profiler timer".to_owned(), move || {
                    loop {
                        sleep(period);
                        if chan.send(MemoryProfilerMsg::Print).is_err() {
                            break;
                        }
                    }
                });
                // Spawn the memory profiler.
                spawn_named("Memory profiler".to_owned(), move || {
                    let memory_profiler = MemoryProfiler::new(port);
                    memory_profiler.start();
                });
            }
            None => {
                // No-op to handle messages when the memory profiler is
                // inactive.
                spawn_named("Memory profiler".to_owned(), move || {
                    loop {
                        match port.recv() {
                            Err(_) | Ok(MemoryProfilerMsg::Exit) => break,
                            _ => {}
                        }
                    }
                });
            }
        }

        MemoryProfilerChan(chan)
    }

    pub fn new(port: Receiver<MemoryProfilerMsg>) -> MemoryProfiler {
        MemoryProfiler {
            port: port
        }
    }

    pub fn start(&self) {
        loop {
            match self.port.recv() {
               Ok(msg) => {
                   if !self.handle_msg(msg) {
                       break
                   }
               }
               _ => break
            }
        }
    }

    fn handle_msg(&self, msg: MemoryProfilerMsg) -> bool {
        match msg {
            MemoryProfilerMsg::Print => {
                self.handle_print_msg();
                true
            },
            MemoryProfilerMsg::Exit => false
        }
    }

    fn print_measurement(path: &str, nbytes: Option<u64>) {
        match nbytes {
            Some(nbytes) => {
                let mebi = 1024f64 * 1024f64;
                println!("{:12.2}: {}", (nbytes as f64) / mebi, path);
            }
            None => {
                println!("{:>12}: {}", "???", path);
            }
        }
    }

    fn handle_print_msg(&self) {
        println!("{:12}: {}", "_size (MiB)_", "_category_");

        // Virtual and physical memory usage, as reported by the OS.
        MemoryProfiler::print_measurement("vsize", get_vsize());
        MemoryProfiler::print_measurement("resident", get_resident());

        for seg in get_resident_segments().iter() {
            MemoryProfiler::print_measurement(seg.0.as_slice(), Some(seg.1));
        }

        // Total number of bytes allocated by the application on the system
        // heap.
        MemoryProfiler::print_measurement("system-heap-allocated",
                                          get_system_heap_allocated());

        // The descriptions of the following jemalloc measurements are taken
        // directly from the jemalloc documentation.

        // "Total number of bytes allocated by the application."
        MemoryProfiler::print_measurement("jemalloc-heap-allocated",
                                          get_jemalloc_stat("stats.allocated"));

        // "Total number of bytes in active pages allocated by the application.
        // This is a multiple of the page size, and greater than or equal to
        // |stats.allocated|."
        MemoryProfiler::print_measurement("jemalloc-heap-active",
                                          get_jemalloc_stat("stats.active"));

        // "Total number of bytes in chunks mapped on behalf of the application.
        // This is a multiple of the chunk size, and is at least as large as
        // |stats.active|. This does not include inactive chunks."
        MemoryProfiler::print_measurement("jemalloc-heap-mapped",
                                          get_jemalloc_stat("stats.mapped"));

        println!("");
    }
}

#[cfg(target_os="linux")]
extern {
    fn mallinfo() -> struct_mallinfo;
}

#[cfg(target_os="linux")]
#[repr(C)]
pub struct struct_mallinfo {
    arena:    c_int,
    ordblks:  c_int,
    smblks:   c_int,
    hblks:    c_int,
    hblkhd:   c_int,
    usmblks:  c_int,
    fsmblks:  c_int,
    uordblks: c_int,
    fordblks: c_int,
    keepcost: c_int,
}

#[cfg(target_os="linux")]
fn get_system_heap_allocated() -> Option<u64> {
    let mut info: struct_mallinfo;
    unsafe {
        info = mallinfo();
    }
    // The documentation in the glibc man page makes it sound like |uordblks|
    // would suffice, but that only gets the small allocations that are put in
    // the brk heap. We need |hblkhd| as well to get the larger allocations
    // that are mmapped.
    Some((info.hblkhd + info.uordblks) as u64)
}

#[cfg(not(target_os="linux"))]
fn get_system_heap_allocated() -> Option<u64> {
    None
}

extern {
    fn je_mallctl(name: *const c_char, oldp: *mut c_void, oldlenp: *mut size_t,
                  newp: *mut c_void, newlen: size_t) -> c_int;
}

fn get_jemalloc_stat(value_name: &str) -> Option<u64> {
    // Before we request the measurement of interest, we first send an "epoch"
    // request. Without that jemalloc gives cached statistics(!) which can be
    // highly inaccurate.
    let epoch_name = "epoch";
    let epoch_c_name = CString::from_slice(epoch_name.as_bytes());
    let mut epoch: u64 = 0;
    let epoch_ptr = &mut epoch as *mut _ as *mut c_void;
    let mut epoch_len = size_of::<u64>() as size_t;

    let value_c_name = CString::from_slice(value_name.as_bytes());
    let mut value: size_t = 0;
    let value_ptr = &mut value as *mut _ as *mut c_void;
    let mut value_len = size_of::<size_t>() as size_t;

    // Using the same values for the `old` and `new` parameters is enough
    // to get the statistics updated.
    let rv = unsafe {
        je_mallctl(epoch_c_name.as_ptr(), epoch_ptr, &mut epoch_len, epoch_ptr,
                   epoch_len)
    };
    if rv != 0 {
        return None;
    }

    let rv = unsafe {
        je_mallctl(value_c_name.as_ptr(), value_ptr, &mut value_len,
                   null_mut(), 0)
    };
    if rv != 0 {
        return None;
    }

    Some(value as u64)
}

// Like std::macros::try!, but for Option<>.
macro_rules! option_try(
    ($e:expr) => (match $e { Some(e) => e, None => return None })
);

#[cfg(target_os="linux")]
fn get_proc_self_statm_field(field: usize) -> Option<u64> {
    let mut f = File::open(&Path::new("/proc/self/statm"));
    match f.read_to_string() {
        Ok(contents) => {
            let s = option_try!(contents.as_slice().words().nth(field));
            let npages = option_try!(s.parse::<u64>().ok());
            Some(npages * (::std::env::page_size() as u64))
        }
        Err(_) => None
    }
}

#[cfg(target_os="linux")]
fn get_vsize() -> Option<u64> {
    get_proc_self_statm_field(0)
}

#[cfg(target_os="linux")]
fn get_resident() -> Option<u64> {
    get_proc_self_statm_field(1)
}

#[cfg(target_os="macos")]
fn get_vsize() -> Option<u64> {
    virtual_size()
}

#[cfg(target_os="macos")]
fn get_resident() -> Option<u64> {
    resident_size()
}

#[cfg(not(any(target_os="linux", target_os = "macos")))]
fn get_vsize() -> Option<u64> {
    None
}

#[cfg(not(any(target_os="linux", target_os = "macos")))]
fn get_resident() -> Option<u64> {
    None
}

#[cfg(target_os="linux")]
fn get_resident_segments() -> Vec<(String, u64)> {
    use regex::Regex;
    use std::collections::HashMap;
    use std::collections::hash_map::Entry;

    // The first line of an entry in /proc/<pid>/smaps looks just like an entry
    // in /proc/<pid>/maps:
    //
    //   address           perms offset  dev   inode  pathname
    //   02366000-025d8000 rw-p 00000000 00:00 0      [heap]
    //
    // Each of the following lines contains a key and a value, separated
    // by ": ", where the key does not contain either of those characters.
    // For example:
    //
    //   Rss:           132 kB

    let path = Path::new("/proc/self/smaps");
    let mut f = ::std::old_io::BufferedReader::new(File::open(&path));

    let seg_re = Regex::new(
        r"^[:xdigit:]+-[:xdigit:]+ (....) [:xdigit:]+ [:xdigit:]+:[:xdigit:]+ \d+ +(.*)").unwrap();
    let rss_re = Regex::new(r"^Rss: +(\d+) kB").unwrap();

    // We record each segment's resident size.
    let mut seg_map: HashMap<String, u64> = HashMap::new();

    #[derive(PartialEq)]
    enum LookingFor { Segment, Rss }
    let mut looking_for = LookingFor::Segment;

    let mut curr_seg_name = String::new();

    // Parse the file.
    for line in f.lines() {
        let line = match line {
            Ok(line) => line,
            Err(_) => continue,
        };
        if looking_for == LookingFor::Segment {
            // Look for a segment info line.
            let cap = match seg_re.captures(line.as_slice()) {
                Some(cap) => cap,
                None => continue,
            };
            let perms = cap.at(1).unwrap();
            let pathname = cap.at(2).unwrap();

            // Construct the segment name from its pathname and permissions.
            curr_seg_name.clear();
            curr_seg_name.push_str("- ");
            if pathname == "" || pathname.starts_with("[stack:") {
                // Anonymous memory. Entries marked with "[stack:nnn]"
                // look like thread stacks but they may include other
                // anonymous mappings, so we can't trust them and just
                // treat them as entirely anonymous.
                curr_seg_name.push_str("anonymous");
            } else {
                curr_seg_name.push_str(pathname);
            }
            curr_seg_name.push_str(" (");
            curr_seg_name.push_str(perms);
            curr_seg_name.push_str(")");

            looking_for = LookingFor::Rss;
        } else {
            // Look for an "Rss:" line.
            let cap = match rss_re.captures(line.as_slice()) {
                Some(cap) => cap,
                None => continue,
            };
            let rss = cap.at(1).unwrap().parse::<u64>().unwrap() * 1024;

            if rss > 0 {
                // Aggregate small segments into "- other".
                let seg_name = if rss < 512 * 1024 {
                    "- other".to_owned()
                } else {
                    curr_seg_name.clone()
                };
                match seg_map.entry(seg_name) {
                    Entry::Vacant(entry) => { entry.insert(rss); },
                    Entry::Occupied(mut entry) => *entry.get_mut() += rss,
                }
            }

            looking_for = LookingFor::Segment;
        }
    }

    let mut segs: Vec<(String, u64)> = seg_map.into_iter().collect();

    // Get the total and add it to the vector. Note that this total differs
    // from the "resident" measurement obtained via /proc/<pid>/statm in
    // get_resident(). It's unclear why this difference occurs; for some
    // processes the measurements match, but for Servo they do not.
    let total = segs.iter().map(|&(_, size)| size).sum();
    segs.push(("resident-according-to-smaps".to_owned(), total));

    // Sort by size; the total will be first.
    segs.sort_by(|&(_, rss1), &(_, rss2)| rss2.cmp(&rss1));

    segs
}

#[cfg(not(target_os="linux"))]
fn get_resident_segments() -> Vec<(String, u64)> {
    vec![]
}

<|endoftext|>


<commit_before>extern crate cards;
extern crate libthyme;
extern crate ncurses;

use super::{Action,UI};
use libthyme::board::{Position,HPosition,VPosition};
use libthyme::game::Game;
use libthyme::score::Scorer;
use std::cmp;

const CARD_WIDTH: i32 = 7;
const CARD_HEIGHT: i32 = 5;
const CARD_MARGIN: i32 = 2;
const BOARD_MARGIN: i32 = 2;
const STATUS_HEIGHT: i32 = 2;

const CARD_COLOR_BLACK: i16 = 1; // black on white
const CARD_COLOR_RED: i16 = 2; // red on white
const CARD_COLOR_EMPTY: i16 = 4; // white on black
const SELECTED_COLOR: i16 = 5; // yellow on black
const CURSOR_INFO_COLOR: i16 = 6; // cyan on black
const GAME_INFO_COLOR: i16 = 7; // green on black
const BG_COLOR: i16 = ncurses::COLOR_BLACK;
const CARD_BG_COLOR: i16 = ncurses::COLOR_WHITE;

/// Set up the UI
pub fn initialize_screen() {
    ncurses::setlocale(ncurses::LcCategory::ctype, "");
    ncurses::initscr();
    ncurses::noecho();
    ncurses::start_color();
    ncurses::keypad(ncurses::stdscr, true);
    ncurses::curs_set(ncurses::CURSOR_VISIBILITY::CURSOR_INVISIBLE);
    ncurses::init_pair(CARD_COLOR_BLACK, ncurses::COLOR_BLACK, CARD_BG_COLOR);
    ncurses::init_pair(CARD_COLOR_RED, ncurses::COLOR_RED, CARD_BG_COLOR);
    ncurses::init_pair(CARD_COLOR_EMPTY, ncurses::COLOR_WHITE, BG_COLOR);
    ncurses::init_pair(SELECTED_COLOR, ncurses::COLOR_YELLOW, BG_COLOR);
    ncurses::init_pair(CURSOR_INFO_COLOR, ncurses::COLOR_CYAN, BG_COLOR);
    ncurses::init_pair(GAME_INFO_COLOR, ncurses::COLOR_GREEN, BG_COLOR);
}

/// Redraw a UI in the current screen
pub fn redraw<T: Scorer>(ui: &UI, game: &mut Game<T>, refresh: bool) {
    if refresh {
        ncurses::clear();
        ncurses::bkgd(ncurses::COLOR_PAIR(CARD_COLOR_EMPTY));
    }
    if validate_screen_size() {
        write_title(game);
        draw_cards(ui, game);
        write_cursor_message(ui, game);
        write_message(&ui.message);
    }
    ncurses::refresh();
}

/// Tear down the UI
pub fn cleanup() {
    ncurses::endwin();
}

/// Process input from the user
///
/// Known inputs:
/// - Arrow keys/WASD: Move cursor between various positions on the board
/// - Q: Quit game
/// - H: Hint (unimplemented)
/// - ?: Help (unimplemented)
/// - Space: Toggle position selection
/// - Return: Play move, clear selection
pub fn get_action() -> Action {
    return match ncurses::getch() {
        ncurses::KEY_LEFT | 97 | 104 => Action::CursorLeft,
        ncurses::KEY_RIGHT | 100 | 108 => Action::CursorRight,
        ncurses::KEY_UP | 119 | 107 => Action::CursorUp,
        ncurses::KEY_DOWN | 115 | 106 => Action::CursorDown,
        ncurses::KEY_RESIZE => Action::Resize,
        ncurses::KEY_ENTER | 13 | 10 => Action::Play,
        32 =>  Action::ToggleSelection, // Space
        113 => Action::Quit, // Q
        116 => Action::Hint, // T
        63 =>  Action::Help, // ?
        _ =>   Action::Unknown
    }
}

/// Check that the content can fit
fn validate_screen_size() -> bool {
    let min_height = BOARD_MARGIN*2 + CARD_MARGIN*4 + STATUS_HEIGHT + CARD_HEIGHT*3;
    if ncurses::LINES < min_height || ncurses::COLS < 50 {
        write_message(&format!(
                "Please resize your terminal to be at least 50x{}",
                min_height));
        return false
    }
    return true
}

/// Print the game title and status info
fn write_title<T: Scorer>(game: &mut Game<T>) {
    printw_margin(0, 0);
    ncurses::attron(ncurses::A_BOLD());
    ncurses::printw("Thyme");
    ncurses::attroff(ncurses::A_BOLD());
    ncurses::printw(&format!(" - Score: {}", game.score()));
    printw_margin(0, 1);
    let (_, suit) = layout_suit(game.board.lucky_card);
    let info = ncurses::COLOR_PAIR(GAME_INFO_COLOR);
    ncurses::attron(info);
    ncurses::printw(&format!("Lucky Suit: {}  Discards Left: {}/{}", suit,
                             game.discards_allowed, game.discards_allowed_max));
    ncurses::clrtoeol();
    ncurses::attroff(info);
}

/// Print the message at the bottom of the window
fn write_message(message: &str) {
    printw_margin(0, ncurses::LINES - 1);
    ncurses::printw(message);
    ncurses::clrtoeol();
}

/// Print a message describing the stack above the cursor
fn write_cursor_message<T: Scorer>(ui: &UI, game: &Game<T>) {
    let stacked_cards = game.board.count_cards(ui.cursor_position);
    let all_cards = game.board.count_all_cards();
    let message = format!("*{}/{} cards in the stack", stacked_cards, all_cards);
    let color = ncurses::COLOR_PAIR(CURSOR_INFO_COLOR);
    printw_margin(0, ncurses::LINES - 2);
    ncurses::attron(color);
    ncurses::printw(&message);
    ncurses::clrtoeol();
    ncurses::attroff(color);
}

/// Print spaces for the width of the board margin
fn printw_margin(x: i32, y: i32) {
    ncurses::mv(y, x);
    printw_repeat(" ", BOARD_MARGIN, ncurses::COLOR_PAIR(CARD_COLOR_EMPTY));
}

/// Print the card values and empty stacks
fn draw_cards<T: Scorer>(ui: &UI, game: &mut Game<T>) {
    for position in game.board.positions() {
        let card = game.board.top(position);
        if card.is_some() {
            draw_card(position, card.unwrap());
        } else {
            draw_empty(game, position);
        }
        let (x, y) = card_location(position);
        toggle_highlight_card(x, y, ui.selection.contains(&position));
        if position == ui.cursor_position {
            let offset = (CARD_WIDTH as f32 /2.0).floor() as i32;
            ncurses::mvprintw(y + CARD_HEIGHT, x + offset, "*");
        }
    }
}

/// Draw a card on the board at a position
fn draw_card(position: Position, card: cards::card::Card) {
    let (x, y) = card_location(position);
    let (color, suit) = layout_suit(card);
    let value = layout_value(card);
    let black = ncurses::COLOR_PAIR(CARD_COLOR_BLACK);
    ncurses::attron(black);
    ncurses::mvprintw(y, x, &value);
    ncurses::attroff(black);
    ncurses::attron(color);
    ncurses::mvprintw(y, x + value.len() as i32, &suit);
    ncurses::attroff(color);
    let spacing = CARD_WIDTH - value.len() as i32 - 1;
    printw_repeat(" ", spacing, black);
    for i in 1..CARD_HEIGHT - 1 {
        ncurses::mv(y + i, x);
        printw_repeat(" ", CARD_WIDTH, black);
    }
    ncurses::mv(y + CARD_HEIGHT - 1, x);
    printw_repeat(" ", spacing, black);
    ncurses::attron(black);
    ncurses::printw(&value);
    ncurses::attroff(black);
    ncurses::attron(color);
    ncurses::printw(&suit);
    ncurses::attroff(color);
}

/// Draw empty slot for a card
fn draw_empty<T: Scorer>(game: &Game<T>, position: Position) {
    let color = ncurses::COLOR_PAIR(CARD_COLOR_EMPTY);
    let (x, y) = card_location(position);
    ncurses::attron(color);
    ncurses::mvprintw(y, x, "┌");
    printw_repeat("─", CARD_WIDTH - 2, color);
    ncurses::mvprintw(y, x + CARD_WIDTH - 1, "┐");
    ncurses::attroff(color);
    let gap_height = CARD_HEIGHT - 1;
    let bonus_height = gap_height/2;
    for i in 1..gap_height {
        ncurses::attron(color);
        ncurses::mvprintw(y + i, x , "│");
        if i == bonus_height {
            let bonus = format!("+{}", game.scorer.bonus(position));
            let available_width = cmp::max(0, CARD_WIDTH - 2 - bonus.len() as i32);
            let lede = available_width/2;
            printw_repeat(" ", lede, color);
            ncurses::printw(&bonus);
            printw_repeat(" ", available_width - lede, color);
        } else {
            printw_repeat(" ", CARD_WIDTH - 2, color);
        }
        ncurses::attroff(color);
        ncurses::attron(color);
        ncurses::mvprintw(y + i, x + CARD_WIDTH - 1, "│");
        ncurses::attroff(color);
    }
    ncurses::attron(color);
    ncurses::mvprintw(y + CARD_HEIGHT - 1, x, "└");
    printw_repeat("─", CARD_WIDTH - 2, color);
    ncurses::mvprintw(y + CARD_HEIGHT - 1, x + CARD_WIDTH - 1, "┘");
    ncurses::attroff(color);
}

/// Print a string repeatedly to fill a length
fn printw_repeat(content: &str, len: i32, color: ncurses::chtype) {
    ncurses::attron(color);
    for _ in 0..len {
       ncurses::printw(content);
    }
    ncurses::attroff(color);
}

/// Print or clear the border around a card
fn toggle_highlight_card(x: i32, y: i32, on: bool) {
    let color = ncurses::COLOR_PAIR(SELECTED_COLOR);
    ncurses::attron(color);
    ncurses::mvprintw(y - 1, x - 1, if on {"┌"} else {" "});
    ncurses::attroff(color);
    ncurses::mv(y - 1, x);
    printw_repeat(if on {"─"} else {" "}, CARD_WIDTH, color);
    ncurses::mv(y + CARD_HEIGHT, x);
    printw_repeat(if on {"─"} else {" "}, CARD_WIDTH, color);
    ncurses::attron(color);
    ncurses::mvprintw(y - 1, x + CARD_WIDTH, if on {"┐"} else {" "});
    for i in 0..CARD_HEIGHT {
        ncurses::mvprintw(y + i, x - 1, if on {"│"} else {" "});
        ncurses::mvprintw(y + i, x + CARD_WIDTH, if on {"│"} else {" "});
    }
    ncurses::mvprintw(y + CARD_HEIGHT, x - 1, if on {"└"} else {" "});
    ncurses::mvprintw(y + CARD_HEIGHT, x + CARD_WIDTH, if on {"┘"} else {" "});
    ncurses::attroff(color);
}

/// Printed representation of a card's suit
fn layout_suit(card: cards::card::Card) -> (ncurses::chtype, String) {
    let black = ncurses::COLOR_PAIR(CARD_COLOR_BLACK);
    let red = ncurses::COLOR_PAIR(CARD_COLOR_RED);
    return match card.suit {
        cards::card::Suit::Diamonds => (red, "\u{2666}".to_string()),
        cards::card::Suit::Clubs => (black, "\u{2663}".to_string()),
        cards::card::Suit::Spades => (black, "\u{2660}".to_string()),
        cards::card::Suit::Hearts => (red, "\u{2665}".to_string()),
    }
}

/// Printed representation of a card's value
fn layout_value(card: cards::card::Card) -> String {
    return match card.value {
        cards::card::Value::Ace => "A",
        cards::card::Value::Two => "2",
        cards::card::Value::Three => "3",
        cards::card::Value::Four => "4",
        cards::card::Value::Five => "5",
        cards::card::Value::Six => "6",
        cards::card::Value::Seven => "7",
        cards::card::Value::Eight => "8",
        cards::card::Value::Nine => "9",
        cards::card::Value::Ten => "10",
        cards::card::Value::Jack => "J",
        cards::card::Value::Queen => "Q",
        cards::card::Value::King => "K",
    }.to_string()
}

/// Location (x, y) for a card position
fn card_location(position: Position) -> (i32, i32) {
    let left = BOARD_MARGIN + CARD_MARGIN;
    let center = BOARD_MARGIN + CARD_MARGIN*2 + CARD_WIDTH;
    let right = BOARD_MARGIN + CARD_MARGIN*3 + CARD_WIDTH*2;
    let top = BOARD_MARGIN + CARD_MARGIN;
    let middle = BOARD_MARGIN + CARD_MARGIN*2 + CARD_HEIGHT;
    let bottom = BOARD_MARGIN + CARD_MARGIN*3 + CARD_HEIGHT*2;
    match (position.x, position.y) {
        (HPosition::Left, VPosition::Top) => (left, top),
        (HPosition::Left, VPosition::Middle) => (left, middle),
        (HPosition::Left, VPosition::Bottom) => (left, bottom),
        (HPosition::Center, VPosition::Top) => (center, top),
        (HPosition::Center, VPosition::Middle) => (center, middle),
        (HPosition::Center, VPosition::Bottom) => (center, bottom),
        (HPosition::Right, VPosition::Top) => (right, top),
        (HPosition::Right, VPosition::Middle) => (right, middle),
        (HPosition::Right, VPosition::Bottom) => (right, bottom),
    }
}
<commit_msg>fix: update ncurses calls where constants became fns<commit_after>extern crate cards;
extern crate libthyme;
extern crate ncurses;

use super::{Action,UI};
use libthyme::board::{Position,HPosition,VPosition};
use libthyme::game::Game;
use libthyme::score::Scorer;
use std::cmp;

const CARD_WIDTH: i32 = 7;
const CARD_HEIGHT: i32 = 5;
const CARD_MARGIN: i32 = 2;
const BOARD_MARGIN: i32 = 2;
const STATUS_HEIGHT: i32 = 2;

const CARD_COLOR_BLACK: i16 = 1; // black on white
const CARD_COLOR_RED: i16 = 2; // red on white
const CARD_COLOR_EMPTY: i16 = 4; // white on black
const SELECTED_COLOR: i16 = 5; // yellow on black
const CURSOR_INFO_COLOR: i16 = 6; // cyan on black
const GAME_INFO_COLOR: i16 = 7; // green on black
const BG_COLOR: i16 = ncurses::COLOR_BLACK;
const CARD_BG_COLOR: i16 = ncurses::COLOR_WHITE;

/// Set up the UI
pub fn initialize_screen() {
    ncurses::setlocale(ncurses::LcCategory::ctype, "");
    ncurses::initscr();
    ncurses::noecho();
    ncurses::start_color();
    ncurses::keypad(ncurses::stdscr(), true);
    ncurses::curs_set(ncurses::CURSOR_VISIBILITY::CURSOR_INVISIBLE);
    ncurses::init_pair(CARD_COLOR_BLACK, ncurses::COLOR_BLACK, CARD_BG_COLOR);
    ncurses::init_pair(CARD_COLOR_RED, ncurses::COLOR_RED, CARD_BG_COLOR);
    ncurses::init_pair(CARD_COLOR_EMPTY, ncurses::COLOR_WHITE, BG_COLOR);
    ncurses::init_pair(SELECTED_COLOR, ncurses::COLOR_YELLOW, BG_COLOR);
    ncurses::init_pair(CURSOR_INFO_COLOR, ncurses::COLOR_CYAN, BG_COLOR);
    ncurses::init_pair(GAME_INFO_COLOR, ncurses::COLOR_GREEN, BG_COLOR);
}

/// Redraw a UI in the current screen
pub fn redraw<T: Scorer>(ui: &UI, game: &mut Game<T>, refresh: bool) {
    if refresh {
        ncurses::clear();
        ncurses::bkgd(ncurses::COLOR_PAIR(CARD_COLOR_EMPTY));
    }
    if validate_screen_size() {
        write_title(game);
        draw_cards(ui, game);
        write_cursor_message(ui, game);
        write_message(&ui.message);
    }
    ncurses::refresh();
}

/// Tear down the UI
pub fn cleanup() {
    ncurses::endwin();
}

/// Process input from the user
///
/// Known inputs:
/// - Arrow keys/WASD: Move cursor between various positions on the board
/// - Q: Quit game
/// - H: Hint (unimplemented)
/// - ?: Help (unimplemented)
/// - Space: Toggle position selection
/// - Return: Play move, clear selection
pub fn get_action() -> Action {
    return match ncurses::getch() {
        ncurses::KEY_LEFT | 97 | 104 => Action::CursorLeft,
        ncurses::KEY_RIGHT | 100 | 108 => Action::CursorRight,
        ncurses::KEY_UP | 119 | 107 => Action::CursorUp,
        ncurses::KEY_DOWN | 115 | 106 => Action::CursorDown,
        ncurses::KEY_RESIZE => Action::Resize,
        ncurses::KEY_ENTER | 13 | 10 => Action::Play,
        32 =>  Action::ToggleSelection, // Space
        113 => Action::Quit, // Q
        116 => Action::Hint, // T
        63 =>  Action::Help, // ?
        _ =>   Action::Unknown
    }
}

/// Check that the content can fit
fn validate_screen_size() -> bool {
    let min_height = BOARD_MARGIN*2 + CARD_MARGIN*4 + STATUS_HEIGHT + CARD_HEIGHT*3;
    if ncurses::LINES() < min_height || ncurses::COLS() < 50 {
        write_message(&format!(
                "Please resize your terminal to be at least 50x{}",
                min_height));
        return false
    }
    return true
}

/// Print the game title and status info
fn write_title<T: Scorer>(game: &mut Game<T>) {
    printw_margin(0, 0);
    ncurses::attron(ncurses::A_BOLD());
    ncurses::printw("Thyme");
    ncurses::attroff(ncurses::A_BOLD());
    ncurses::printw(&format!(" - Score: {}", game.score()));
    printw_margin(0, 1);
    let (_, suit) = layout_suit(game.board.lucky_card);
    let info = ncurses::COLOR_PAIR(GAME_INFO_COLOR);
    ncurses::attron(info);
    ncurses::printw(&format!("Lucky Suit: {}  Discards Left: {}/{}", suit,
                             game.discards_allowed, game.discards_allowed_max));
    ncurses::clrtoeol();
    ncurses::attroff(info);
}

/// Print the message at the bottom of the window
fn write_message(message: &str) {
    printw_margin(0, ncurses::LINES() - 1);
    ncurses::printw(message);
    ncurses::clrtoeol();
}

/// Print a message describing the stack above the cursor
fn write_cursor_message<T: Scorer>(ui: &UI, game: &Game<T>) {
    let stacked_cards = game.board.count_cards(ui.cursor_position);
    let all_cards = game.board.count_all_cards();
    let message = format!("*{}/{} cards in the stack", stacked_cards, all_cards);
    let color = ncurses::COLOR_PAIR(CURSOR_INFO_COLOR);
    printw_margin(0, ncurses::LINES() - 2);
    ncurses::attron(color);
    ncurses::printw(&message);
    ncurses::clrtoeol();
    ncurses::attroff(color);
}

/// Print spaces for the width of the board margin
fn printw_margin(x: i32, y: i32) {
    ncurses::mv(y, x);
    printw_repeat(" ", BOARD_MARGIN, ncurses::COLOR_PAIR(CARD_COLOR_EMPTY));
}

/// Print the card values and empty stacks
fn draw_cards<T: Scorer>(ui: &UI, game: &mut Game<T>) {
    for position in game.board.positions() {
        let card = game.board.top(position);
        if card.is_some() {
            draw_card(position, card.unwrap());
        } else {
            draw_empty(game, position);
        }
        let (x, y) = card_location(position);
        toggle_highlight_card(x, y, ui.selection.contains(&position));
        if position == ui.cursor_position {
            let offset = (CARD_WIDTH as f32 /2.0).floor() as i32;
            ncurses::mvprintw(y + CARD_HEIGHT, x + offset, "*");
        }
    }
}

/// Draw a card on the board at a position
fn draw_card(position: Position, card: cards::card::Card) {
    let (x, y) = card_location(position);
    let (color, suit) = layout_suit(card);
    let value = layout_value(card);
    let black = ncurses::COLOR_PAIR(CARD_COLOR_BLACK);
    ncurses::attron(black);
    ncurses::mvprintw(y, x, &value);
    ncurses::attroff(black);
    ncurses::attron(color);
    ncurses::mvprintw(y, x + value.len() as i32, &suit);
    ncurses::attroff(color);
    let spacing = CARD_WIDTH - value.len() as i32 - 1;
    printw_repeat(" ", spacing, black);
    for i in 1..CARD_HEIGHT - 1 {
        ncurses::mv(y + i, x);
        printw_repeat(" ", CARD_WIDTH, black);
    }
    ncurses::mv(y + CARD_HEIGHT - 1, x);
    printw_repeat(" ", spacing, black);
    ncurses::attron(black);
    ncurses::printw(&value);
    ncurses::attroff(black);
    ncurses::attron(color);
    ncurses::printw(&suit);
    ncurses::attroff(color);
}

/// Draw empty slot for a card
fn draw_empty<T: Scorer>(game: &Game<T>, position: Position) {
    let color = ncurses::COLOR_PAIR(CARD_COLOR_EMPTY);
    let (x, y) = card_location(position);
    ncurses::attron(color);
    ncurses::mvprintw(y, x, "┌");
    printw_repeat("─", CARD_WIDTH - 2, color);
    ncurses::mvprintw(y, x + CARD_WIDTH - 1, "┐");
    ncurses::attroff(color);
    let gap_height = CARD_HEIGHT - 1;
    let bonus_height = gap_height/2;
    for i in 1..gap_height {
        ncurses::attron(color);
        ncurses::mvprintw(y + i, x , "│");
        if i == bonus_height {
            let bonus = format!("+{}", game.scorer.bonus(position));
            let available_width = cmp::max(0, CARD_WIDTH - 2 - bonus.len() as i32);
            let lede = available_width/2;
            printw_repeat(" ", lede, color);
            ncurses::printw(&bonus);
            printw_repeat(" ", available_width - lede, color);
        } else {
            printw_repeat(" ", CARD_WIDTH - 2, color);
        }
        ncurses::attroff(color);
        ncurses::attron(color);
        ncurses::mvprintw(y + i, x + CARD_WIDTH - 1, "│");
        ncurses::attroff(color);
    }
    ncurses::attron(color);
    ncurses::mvprintw(y + CARD_HEIGHT - 1, x, "└");
    printw_repeat("─", CARD_WIDTH - 2, color);
    ncurses::mvprintw(y + CARD_HEIGHT - 1, x + CARD_WIDTH - 1, "┘");
    ncurses::attroff(color);
}

/// Print a string repeatedly to fill a length
fn printw_repeat(content: &str, len: i32, color: ncurses::chtype) {
    ncurses::attron(color);
    for _ in 0..len {
       ncurses::printw(content);
    }
    ncurses::attroff(color);
}

/// Print or clear the border around a card
fn toggle_highlight_card(x: i32, y: i32, on: bool) {
    let color = ncurses::COLOR_PAIR(SELECTED_COLOR);
    ncurses::attron(color);
    ncurses::mvprintw(y - 1, x - 1, if on {"┌"} else {" "});
    ncurses::attroff(color);
    ncurses::mv(y - 1, x);
    printw_repeat(if on {"─"} else {" "}, CARD_WIDTH, color);
    ncurses::mv(y + CARD_HEIGHT, x);
    printw_repeat(if on {"─"} else {" "}, CARD_WIDTH, color);
    ncurses::attron(color);
    ncurses::mvprintw(y - 1, x + CARD_WIDTH, if on {"┐"} else {" "});
    for i in 0..CARD_HEIGHT {
        ncurses::mvprintw(y + i, x - 1, if on {"│"} else {" "});
        ncurses::mvprintw(y + i, x + CARD_WIDTH, if on {"│"} else {" "});
    }
    ncurses::mvprintw(y + CARD_HEIGHT, x - 1, if on {"└"} else {" "});
    ncurses::mvprintw(y + CARD_HEIGHT, x + CARD_WIDTH, if on {"┘"} else {" "});
    ncurses::attroff(color);
}

/// Printed representation of a card's suit
fn layout_suit(card: cards::card::Card) -> (ncurses::chtype, String) {
    let black = ncurses::COLOR_PAIR(CARD_COLOR_BLACK);
    let red = ncurses::COLOR_PAIR(CARD_COLOR_RED);
    return match card.suit {
        cards::card::Suit::Diamonds => (red, "\u{2666}".to_string()),
        cards::card::Suit::Clubs => (black, "\u{2663}".to_string()),
        cards::card::Suit::Spades => (black, "\u{2660}".to_string()),
        cards::card::Suit::Hearts => (red, "\u{2665}".to_string()),
    }
}

/// Printed representation of a card's value
fn layout_value(card: cards::card::Card) -> String {
    return match card.value {
        cards::card::Value::Ace => "A",
        cards::card::Value::Two => "2",
        cards::card::Value::Three => "3",
        cards::card::Value::Four => "4",
        cards::card::Value::Five => "5",
        cards::card::Value::Six => "6",
        cards::card::Value::Seven => "7",
        cards::card::Value::Eight => "8",
        cards::card::Value::Nine => "9",
        cards::card::Value::Ten => "10",
        cards::card::Value::Jack => "J",
        cards::card::Value::Queen => "Q",
        cards::card::Value::King => "K",
    }.to_string()
}

/// Location (x, y) for a card position
fn card_location(position: Position) -> (i32, i32) {
    let left = BOARD_MARGIN + CARD_MARGIN;
    let center = BOARD_MARGIN + CARD_MARGIN*2 + CARD_WIDTH;
    let right = BOARD_MARGIN + CARD_MARGIN*3 + CARD_WIDTH*2;
    let top = BOARD_MARGIN + CARD_MARGIN;
    let middle = BOARD_MARGIN + CARD_MARGIN*2 + CARD_HEIGHT;
    let bottom = BOARD_MARGIN + CARD_MARGIN*3 + CARD_HEIGHT*2;
    match (position.x, position.y) {
        (HPosition::Left, VPosition::Top) => (left, top),
        (HPosition::Left, VPosition::Middle) => (left, middle),
        (HPosition::Left, VPosition::Bottom) => (left, bottom),
        (HPosition::Center, VPosition::Top) => (center, top),
        (HPosition::Center, VPosition::Middle) => (center, middle),
        (HPosition::Center, VPosition::Bottom) => (center, bottom),
        (HPosition::Right, VPosition::Top) => (right, top),
        (HPosition::Right, VPosition::Middle) => (right, middle),
        (HPosition::Right, VPosition::Bottom) => (right, bottom),
    }
}
<|endoftext|>


<commit_before>use rosxmlrpc;
use std;

pub struct Master {
    client: rosxmlrpc::Client,
    client_id: String,
    caller_api: String,
}

impl Master {
    pub fn new(client_id: &str, caller_api: &str) -> Master {
        let master_uri = std::env::var("ROS_MASTER_URI")
            .unwrap_or("http://localhost:11311/".to_owned());
        Master {
            client: rosxmlrpc::Client::new(&master_uri),
            client_id: client_id.to_owned(),
            caller_api: caller_api.to_owned(),
        }
    }

    pub fn register_service(&self,
                            service: &str,
                            service_api: &str)
                            -> rosxmlrpc::client::ClientResult {
        self.client
            .request("registerService",
                     &[self.client_id.as_str(), service, service_api, self.caller_api.as_str()])
    }

    pub fn unregister_service(&self,
                              service: &str,
                              service_api: &str)
                              -> rosxmlrpc::client::ClientResult {
        self.client
            .request("unregisterService",
                     &[self.client_id.as_str(), service, service_api])
    }

    pub fn register_subscriber(&self,
                               topic: &str,
                               topic_type: &str)
                               -> rosxmlrpc::client::ClientResult {
        self.client
            .request("registerSubscriber",
                     &[self.client_id.as_str(), topic, topic_type, self.caller_api.as_str()])
    }

    pub fn unregister_subscriber(&self, topic: &str) -> rosxmlrpc::client::ClientResult {
        self.client
            .request("unregisterSubscriber",
                     &[self.client_id.as_str(), topic, self.caller_api.as_str()])
    }

    pub fn register_publisher(&self,
                              topic: &str,
                              topic_type: &str)
                              -> rosxmlrpc::client::ClientResult {
        self.client
            .request("registerPublisher",
                     &[self.client_id.as_str(), topic, topic_type, self.caller_api.as_str()])
    }

    pub fn unregister_publisher(&self, topic: &str) -> rosxmlrpc::client::ClientResult {
        self.client
            .request("unregisterPublisher",
                     &[self.client_id.as_str(), topic, self.caller_api.as_str()])
    }
}
<commit_msg>Finish Master API listing<commit_after>use rosxmlrpc;
use std;

pub struct Master {
    client: rosxmlrpc::Client,
    client_id: String,
    caller_api: String,
}

impl Master {
    pub fn new(client_id: &str, caller_api: &str) -> Master {
        let master_uri = std::env::var("ROS_MASTER_URI")
            .unwrap_or("http://localhost:11311/".to_owned());
        Master {
            client: rosxmlrpc::Client::new(&master_uri),
            client_id: client_id.to_owned(),
            caller_api: caller_api.to_owned(),
        }
    }

    pub fn register_service(&self,
                            service: &str,
                            service_api: &str)
                            -> rosxmlrpc::client::ClientResult {
        self.client
            .request("registerService",
                     &[self.client_id.as_str(), service, service_api, self.caller_api.as_str()])
    }

    pub fn unregister_service(&self,
                              service: &str,
                              service_api: &str)
                              -> rosxmlrpc::client::ClientResult {
        self.client
            .request("unregisterService",
                     &[self.client_id.as_str(), service, service_api])
    }

    pub fn register_subscriber(&self,
                               topic: &str,
                               topic_type: &str)
                               -> rosxmlrpc::client::ClientResult {
        self.client
            .request("registerSubscriber",
                     &[self.client_id.as_str(), topic, topic_type, self.caller_api.as_str()])
    }

    pub fn unregister_subscriber(&self, topic: &str) -> rosxmlrpc::client::ClientResult {
        self.client
            .request("unregisterSubscriber",
                     &[self.client_id.as_str(), topic, self.caller_api.as_str()])
    }

    pub fn register_publisher(&self,
                              topic: &str,
                              topic_type: &str)
                              -> rosxmlrpc::client::ClientResult {
        self.client
            .request("registerPublisher",
                     &[self.client_id.as_str(), topic, topic_type, self.caller_api.as_str()])
    }

    pub fn unregister_publisher(&self, topic: &str) -> rosxmlrpc::client::ClientResult {
        self.client
            .request("unregisterPublisher",
                     &[self.client_id.as_str(), topic, self.caller_api.as_str()])
    }

    pub fn lookup_node(&self, node_name: &str) -> rosxmlrpc::client::ClientResult {
        self.client
            .request("lookupNode", &[self.client_id.as_str(), node_name])
    }

    pub fn get_published_topics(&self, subgraph: &str) -> rosxmlrpc::client::ClientResult {
        self.client
            .request("getPublishedTopics", &[self.client_id.as_str(), subgraph])
    }

    pub fn get_topic_types(&self) -> rosxmlrpc::client::ClientResult {
        self.client
            .request("getTopicTypes", &[self.client_id.as_str()])
    }

    pub fn get_system_state(&self) -> rosxmlrpc::client::ClientResult {
        self.client
            .request("getSystemState", &[self.client_id.as_str()])
    }

    pub fn get_uri(&self) -> rosxmlrpc::client::ClientResult {
        self.client
            .request("getUri", &[self.client_id.as_str()])
    }

    pub fn lookup_service(&self, service: &str) -> rosxmlrpc::client::ClientResult {
        self.client
            .request("lookupService", &[self.client_id.as_str(), service])
    }
}
<|endoftext|>