<commit_before>extern crate byteorder;
extern crate flate2;
extern crate getopts;
extern crate tempdir;
#[macro_use]
extern crate error_chain;

use std::fs::File;
use std::io;
use std::io::{BufReader, BufWriter};
use std::path::Path;
use std::env;
use std::process::exit;
use std::process::Command;

use getopts::Options;

use tempdir::TempDir;

pub mod data;
mod write;
mod read;
mod string_write;
mod string_read;
#[cfg(test)]
mod tests;

mod errors {
    error_chain!{
        foreign_links {
            Io(::std::io::Error);
            FromUtf8String(::std::string::FromUtf8Error);
            FromUtf8Str(::std::str::Utf8Error);
        }
    }
}
use errors::*;

fn main() {
    match run_cmdline() {
        Ok(ret) => {
            exit(ret);
        },
        Err(ref e) => {
            if let Some(backtrace) = e.backtrace() {
                eprintln!("{:?}", backtrace);
            }

            eprintln!("Error: {}", e);

            for e in e.iter().skip(1) {
                eprintln!("	caused by: {}", e);
            }

            eprintln!("For help, run with --help or read the manpage.");
            exit(1);
        },
    }
}

/// Main entrypoint for program.
///
/// Returns an integer representing the program's exit status.
fn run_cmdline() -> Result<i32> {
    let args: Vec<String> = env::args().collect();

    let mut opts = Options::new();
    opts.optflagopt("e", "edit", "edit a NBT file with your $EDITOR.
    If [FILE] is specified, then that file is edited in place, but specifying --input and/or --output will override the input/output.
    If no file is specified, default to read from --input and writing to --output.", "FILE");
    opts.optflagopt("p", "print", "print NBT file to text format. Adding an argument to this is the same as specifying --input", "FILE");
    opts.optflagopt("r", "reverse", "reverse a file in text format to NBT format. Adding an argument to this is the same as specifying --input", "FILE");
    opts.optopt("i",
                "input",
                "specify the input file, defaults to stdin",
                "FILE");
    opts.optopt("o",
                "output",
                "specify the output file, defaults to stdout",
                "FILE");
    opts.optflag("", "man", "print the nbted man page source and exit");
    opts.optflag("h", "help", "print the help menu and exit");
    opts.optflag("", "version", "print program version and exit");

    let matches = opts.parse(&args[1..]).chain_err(|| "error parsing options")?;

    if matches.opt_present("h") {
        let brief = "Usage: nbted [options] FILE";
        print!("{}", opts.usage(&brief));
        println!("\nThe default action, taken if no action is explicitly selected, is to --edit.");
        println!("\nFor detailed usage information, read the nbted man page. If the nbted man page\
        \nwas not installed on your system, such as if you installed using `cargo install`,\
        \nthen you can use `nbted --man | nroff -man | less` to read the nbted man page.");
        return Ok(0);
    }

    if matches.opt_present("version") {
        println!("{} {} {}",
                 env!("CARGO_PKG_NAME"),
                 env!("CARGO_PKG_VERSION"),
                 /* See build.rs for the git-revision.txt file */
                 include!(concat!(env!("OUT_DIR"), "/git-revision.txt")));
        println!("https://github.com/C4K3/nbted");
        return Ok(0);
    }

    if matches.opt_present("man") {
        print!(include_str!("../nbted.1"));
        return Ok(0);
    }

    let is_print: bool = matches.opt_present("print");
    let is_reverse: bool = matches.opt_present("reverse");
    let is_edit: bool = if matches.opt_present("edit") {
        true
    } else {
        /* If edit is not explicitly defined, it is the default action and is
         * selected if no other action is specified */
        (!(is_reverse || is_print))
    };

    /* Hopefully this is a simpler way of ensuring that only one action can be
     * taken than having a long logical expression */
    let mut action_count = 0;
    if is_print {
        action_count += 1;
    }
    if is_reverse {
        action_count += 1;
    }
    if is_edit {
        action_count += 1;
    }
    if action_count > 1 {
        bail!("You can only specify one action at a time.");
    }

    /* Figure out the input file, by trying to read the arguments for all of
     * --input, --edit, --print and --reverse, prioritizing --input over the
     * other arguments, if none of the arguments are specified but there is a
     * free argument, use that, else we finally default to - (stdin) */
    let input = if let Some(x) = matches.opt_str("input") {
        x
    } else if let Some(x) = matches.opt_str("edit") {
        x
    } else if let Some(x) = matches.opt_str("print") {
        x
    } else if let Some(x) = matches.opt_str("reverse") {
        x
    } else if matches.free.len() == 1 {
        matches.free[0].clone()
    } else {
        /* stdin */
        "-".to_string()
    };

    let output = if let Some(x) = matches.opt_str("output") {
        x
    } else if let Some(x) = matches.opt_str("edit") {
        x
    } else if is_edit && matches.free.len() == 1 {
        /* Only want to default to the free argument if we're editing
         * (DO NOT WRITE BACK TO THE READ FILE UNLESS EDITING!) */
        matches.free[0].clone()
    } else {
        /* stdout */
        "-".to_string()
    };

    if matches.free.len() > 1 {
        bail!("nbted was given multiple arguments, but only supports editing one file at a time.");
    }

    if is_print {
        return print(&input, &output);
    } else if is_reverse {
        return reverse(&input, &output);
    } else if is_edit {
        return edit(&input, &output);
    } else {
        bail!("Internal error: No action selected. (Please report this.)");
    }
}

/// When the user wants to edit a specific file in place
///
/// Returns an integer representing the program's exit status.
fn edit(input: &str, output: &str) -> Result<i32> {

    /* First we read the NBT data from the input */
    let nbt = if input == "-" {
        // let mut f = BufReader::new(io::stdin());
        let f = io::stdin();
        let mut f = f.lock();
        read::read_file(&mut f).chain_err(|| "Unable to parse any NBT files from stdin")?
    } else {
        let path: &Path = Path::new(input);
        let f = File::open(path)
            .chain_err(|| format!("Unable to open file {}", input))?;
        let mut f = BufReader::new(f);

        read::read_file(&mut f).chain_err(
            || format!("Unable to parse {}, are you sure it's an NBT file?",
                       input))?
    };

    /* Then we create a temporary file and write the NBT data in text format
     * to the temporary file */
    let tmpdir = TempDir::new("nbted")
        .chain_err(|| "Unable to create temporary directory")?;

    let tmp = match Path::new(input).file_name() {
        Some(x) => {
            let mut x = x.to_os_string();
            x.push(".txt");
            x
        },
        None => bail!("Error reading file name"),
    };
    let tmp_path = tmpdir.path().join(tmp);

    {
        let mut f = File::create(&tmp_path)
            .chain_err(|| "Unable to create temporary file")?;

        string_write::write_file(&mut f, &nbt)
            .chain_err(|| "Unable to write temporary file")?;

        f.sync_all().chain_err(|| "Unable to synchronize file")?;
    }

    let new_nbt = {
        let mut new_nbt = open_editor(&tmp_path);

        while let Err(e) = new_nbt {
            eprintln!("Unable to parse edited file");
            for e in e.iter() {
                eprintln!("	caused by: {}", e);
            }
            eprintln!("Do you want to open the file for editing again? (y/N)");

            let mut line = String::new();
            io::stdin().read_line(&mut line)
                .chain_err(|| "Error reading from stdin. Nothing was changed")?;

            if line.trim() == "y" {
                new_nbt = open_editor(&tmp_path);
            } else {
                eprintln!("Exiting ... File is unchanged.");
                return Ok(0);
            }
        }

        new_nbt.expect("new_nbt was Error")
    };

    if nbt == new_nbt {
        eprintln!("No changes, will do nothing.");
        return Ok(0);
    }

    /* And finally we write the edited nbt (new_nbt) into the output file */
    if output == "-" {
        let f = io::stdout();
        let mut f = f.lock();
        /* If we get an error writing to stdout, we want to just silently exit
         * with exit code 1. (It can generally be assumed that nbted will not
         * error in serializing the data, so any error here would be because of
         * writing to stdout) */
        match write::write_file(&mut f, &new_nbt) {
            Ok(()) => (),
            Err(_) => return Ok(1),
        }
    } else {
        let path: &Path = Path::new(output);
        let f = File::create(&path).chain_err(
            || format!("Unable to write to output NBT file {}. Nothing was changed",
                       output))?;
        let mut f = BufWriter::new(f);

        write::write_file(&mut f, &new_nbt).chain_err(
            || format!("Error writing NBT file {}. State of NBT file is unknown, consider restoring it from a backup.",
                       output))?;
    }

    eprintln!("File edited successfully.");
    Ok(0)
}

/// Open the user's $EDITOR on the temporary file, wait until the editor is
/// closed again, read the temporary file and attempt to parse it into NBT,
/// returning the result.
fn open_editor(tmp_path: &Path) -> Result<data::NBTFile> {
    let editor = match env::var("EDITOR") {
        Ok(x) => x,
        Err(_) => {
            match env::var("VISUAL") {
                Ok(x) => x,
                Err(_) => bail!("Unable to find $EDITOR"),
            }
        },
    };

    let mut cmd = Command::new(editor);
    cmd.arg(&tmp_path.as_os_str());
    let mut cmd = cmd.spawn().chain_err(|| "Error opening editor")?;

    match cmd.wait() {
        Ok(x) if x.success() => (),
        Ok(_) => bail!("Editor did not exit correctly"),
        Err(e) => {
            return Err(Error::with_chain(e, "Error executing editor"));
        },
    }

    /* Then we parse the text format in the temporary file into NBT */
    let mut f = File::open(&tmp_path).chain_err(
        || "Unable to read temporary file. Nothing was changed.")?;

    string_read::read_file(&mut f).map_err(|e| e.into())
}

/// When the user wants to print an NBT file to text format
fn print(input: &str, output: &str) -> Result<i32> {
    /* First we read a NBTFile from the input */
    let nbt = if input == "-" {
        let f = io::stdin();
        let mut f = f.lock();
        read::read_file(&mut f).chain_err(
            || format!("Unable to parse {}, are you sure it's an NBT file?",
                       input))?
    } else {
        let path: &Path = Path::new(input);
        let f = File::open(path).chain_err(
            || format!("Unable to open file {}", input))?;
        let mut f = BufReader::new(f);

        read::read_file(&mut f).chain_err(
            || format!("Unable to parse {}, are you sure it's an NBT file?",
                       input))?
    };

    /* Then we write the NBTFile to the output in text format */
    if output == "-" {
        let f = io::stdout();
        let mut f = f.lock();
        /* If we get an error writing to stdout, we want to just silently exit
         * with exit code 1. (It can generally be assumed that nbted will not
         * error in serializing the data, so any error here would be because of
         * writing to stdout) */
        match string_write::write_file(&mut f, &nbt) {
            Ok(()) => (),
            Err(_) => return Ok(1),
        }
    } else {
        let path: &Path = Path::new(output);
        let f = File::create(&path).chain_err(
            || format!("Unable to write to output NBT file {}. Nothing was changed.",
                       output))?;
        let mut f = BufWriter::new(f);

        string_write::write_file(&mut f, &nbt).chain_err(
            || format!("Error writing NBT file {}. State of NBT file is unknown, consider restoring it from a backup.",
                       output))?;
    }

    Ok(0)
}

/// When the user wants to convert a text format file into an NBT file
///
/// Returns an integer representing the program's exit status.
fn reverse(input: &str, output: &str) -> Result<i32> {
    /* First we read the input file in the text format */
    let path: &Path = Path::new(input);
    let mut f = File::open(&path).chain_err(
        || format!("Unable to read text file {}",
                   input))?;

    let nbt = string_read::read_file(&mut f).chain_err(
        || format!("Unable to parse text file {}",
        input))?;

    /* Then we write the parsed NBT to the output file in NBT format */
    if output == "-" {
        let f = io::stdout();
        let mut f = f.lock();
        /* If we get an error writing to stdout, we want to just silently exit
         * with exit code 1. (It can generally be assumed that nbted will not
         * error in serializing the data, so any error here would be because of
         * writing to stdout) */
        match write::write_file(&mut f, &nbt) {
            Ok(()) => (),
            Err(_) => return Ok(1),
        }
    } else {
        let path: &Path = Path::new(output);
        let f = File::create(&path).chain_err(
            || format!("Unable to write to output NBT file {}. Nothing was changed",
                       output))?;
        let mut f = BufWriter::new(f);

        write::write_file(&mut f, &nbt).chain_err(
            || format!("error writing to NBT FILE {}, state of NBT file is unknown, consider restoring it from a backup.",
                       output))?;
    }

    Ok(0)
}
<commit_msg>Enable additional lints and fix their warnings<commit_after>#![warn(unused_results,
        unused_extern_crates,
        unused_import_braces,
        unused_qualifications,
        variant_size_differences,
        trivial_casts,
        trivial_numeric_casts,
        )]

extern crate byteorder;
extern crate flate2;
extern crate getopts;
extern crate tempdir;
#[macro_use]
extern crate error_chain;

use std::fs::File;
use std::io;
use std::io::{BufReader, BufWriter};
use std::path::Path;
use std::env;
use std::process::exit;
use std::process::Command;

use getopts::Options;

use tempdir::TempDir;

pub mod data;
mod write;
mod read;
mod string_write;
mod string_read;
#[cfg(test)]
mod tests;

mod errors {
    error_chain!{
        foreign_links {
            Io(::std::io::Error);
            FromUtf8String(::std::string::FromUtf8Error);
            FromUtf8Str(::std::str::Utf8Error);
        }
    }
}
use errors::*;

fn main() {
    match run_cmdline() {
        Ok(ret) => {
            exit(ret);
        },
        Err(ref e) => {
            if let Some(backtrace) = e.backtrace() {
                eprintln!("{:?}", backtrace);
            }

            eprintln!("Error: {}", e);

            for e in e.iter().skip(1) {
                eprintln!("	caused by: {}", e);
            }

            eprintln!("For help, run with --help or read the manpage.");
            exit(1);
        },
    }
}

/// Main entrypoint for program.
///
/// Returns an integer representing the program's exit status.
fn run_cmdline() -> Result<i32> {
    let args: Vec<String> = env::args().collect();

    let mut opts = Options::new();
    let _: &Options = opts.optflagopt("e", "edit", "edit a NBT file with your $EDITOR.
    If [FILE] is specified, then that file is edited in place, but specifying --input and/or --output will override the input/output.
    If no file is specified, default to read from --input and writing to --output.", "FILE");
    let _: &Options = opts.optflagopt("p", "print", "print NBT file to text format. Adding an argument to this is the same as specifying --input", "FILE");
    let _: &Options = opts.optflagopt("r", "reverse", "reverse a file in text format to NBT format. Adding an argument to this is the same as specifying --input", "FILE");
    let _: &Options = opts.optopt("i",
                "input",
                "specify the input file, defaults to stdin",
                "FILE");
    let _: &Options = opts.optopt("o",
                "output",
                "specify the output file, defaults to stdout",
                "FILE");
    let _: &Options = opts.optflag("", "man", "print the nbted man page source and exit");
    let _: &Options = opts.optflag("h", "help", "print the help menu and exit");
    let _: &Options = opts.optflag("", "version", "print program version and exit");

    let matches = opts.parse(&args[1..]).chain_err(|| "error parsing options")?;

    if matches.opt_present("h") {
        let brief = "Usage: nbted [options] FILE";
        print!("{}", opts.usage(&brief));
        println!("\nThe default action, taken if no action is explicitly selected, is to --edit.");
        println!("\nFor detailed usage information, read the nbted man page. If the nbted man page\
        \nwas not installed on your system, such as if you installed using `cargo install`,\
        \nthen you can use `nbted --man | nroff -man | less` to read the nbted man page.");
        return Ok(0);
    }

    if matches.opt_present("version") {
        println!("{} {} {}",
                 env!("CARGO_PKG_NAME"),
                 env!("CARGO_PKG_VERSION"),
                 /* See build.rs for the git-revision.txt file */
                 include!(concat!(env!("OUT_DIR"), "/git-revision.txt")));
        println!("https://github.com/C4K3/nbted");
        return Ok(0);
    }

    if matches.opt_present("man") {
        print!(include_str!("../nbted.1"));
        return Ok(0);
    }

    let is_print: bool = matches.opt_present("print");
    let is_reverse: bool = matches.opt_present("reverse");
    let is_edit: bool = if matches.opt_present("edit") {
        true
    } else {
        /* If edit is not explicitly defined, it is the default action and is
         * selected if no other action is specified */
        (!(is_reverse || is_print))
    };

    /* Hopefully this is a simpler way of ensuring that only one action can be
     * taken than having a long logical expression */
    let mut action_count = 0;
    if is_print {
        action_count += 1;
    }
    if is_reverse {
        action_count += 1;
    }
    if is_edit {
        action_count += 1;
    }
    if action_count > 1 {
        bail!("You can only specify one action at a time.");
    }

    /* Figure out the input file, by trying to read the arguments for all of
     * --input, --edit, --print and --reverse, prioritizing --input over the
     * other arguments, if none of the arguments are specified but there is a
     * free argument, use that, else we finally default to - (stdin) */
    let input = if let Some(x) = matches.opt_str("input") {
        x
    } else if let Some(x) = matches.opt_str("edit") {
        x
    } else if let Some(x) = matches.opt_str("print") {
        x
    } else if let Some(x) = matches.opt_str("reverse") {
        x
    } else if matches.free.len() == 1 {
        matches.free[0].clone()
    } else {
        /* stdin */
        "-".to_string()
    };

    let output = if let Some(x) = matches.opt_str("output") {
        x
    } else if let Some(x) = matches.opt_str("edit") {
        x
    } else if is_edit && matches.free.len() == 1 {
        /* Only want to default to the free argument if we're editing
         * (DO NOT WRITE BACK TO THE READ FILE UNLESS EDITING!) */
        matches.free[0].clone()
    } else {
        /* stdout */
        "-".to_string()
    };

    if matches.free.len() > 1 {
        bail!("nbted was given multiple arguments, but only supports editing one file at a time.");
    }

    if is_print {
        return print(&input, &output);
    } else if is_reverse {
        return reverse(&input, &output);
    } else if is_edit {
        return edit(&input, &output);
    } else {
        bail!("Internal error: No action selected. (Please report this.)");
    }
}

/// When the user wants to edit a specific file in place
///
/// Returns an integer representing the program's exit status.
fn edit(input: &str, output: &str) -> Result<i32> {

    /* First we read the NBT data from the input */
    let nbt = if input == "-" {
        // let mut f = BufReader::new(io::stdin());
        let f = io::stdin();
        let mut f = f.lock();
        read::read_file(&mut f).chain_err(|| "Unable to parse any NBT files from stdin")?
    } else {
        let path: &Path = Path::new(input);
        let f = File::open(path)
            .chain_err(|| format!("Unable to open file {}", input))?;
        let mut f = BufReader::new(f);

        read::read_file(&mut f).chain_err(
            || format!("Unable to parse {}, are you sure it's an NBT file?",
                       input))?
    };

    /* Then we create a temporary file and write the NBT data in text format
     * to the temporary file */
    let tmpdir = TempDir::new("nbted")
        .chain_err(|| "Unable to create temporary directory")?;

    let tmp = match Path::new(input).file_name() {
        Some(x) => {
            let mut x = x.to_os_string();
            x.push(".txt");
            x
        },
        None => bail!("Error reading file name"),
    };
    let tmp_path = tmpdir.path().join(tmp);

    {
        let mut f = File::create(&tmp_path)
            .chain_err(|| "Unable to create temporary file")?;

        string_write::write_file(&mut f, &nbt)
            .chain_err(|| "Unable to write temporary file")?;

        f.sync_all().chain_err(|| "Unable to synchronize file")?;
    }

    let new_nbt = {
        let mut new_nbt = open_editor(&tmp_path);

        while let Err(e) = new_nbt {
            eprintln!("Unable to parse edited file");
            for e in e.iter() {
                eprintln!("	caused by: {}", e);
            }
            eprintln!("Do you want to open the file for editing again? (y/N)");

            let mut line = String::new();
            let _: usize = io::stdin().read_line(&mut line)
                .chain_err(|| "Error reading from stdin. Nothing was changed")?;

            if line.trim() == "y" {
                new_nbt = open_editor(&tmp_path);
            } else {
                eprintln!("Exiting ... File is unchanged.");
                return Ok(0);
            }
        }

        new_nbt.expect("new_nbt was Error")
    };

    if nbt == new_nbt {
        eprintln!("No changes, will do nothing.");
        return Ok(0);
    }

    /* And finally we write the edited nbt (new_nbt) into the output file */
    if output == "-" {
        let f = io::stdout();
        let mut f = f.lock();
        /* If we get an error writing to stdout, we want to just silently exit
         * with exit code 1. (It can generally be assumed that nbted will not
         * error in serializing the data, so any error here would be because of
         * writing to stdout) */
        match write::write_file(&mut f, &new_nbt) {
            Ok(()) => (),
            Err(_) => return Ok(1),
        }
    } else {
        let path: &Path = Path::new(output);
        let f = File::create(&path).chain_err(
            || format!("Unable to write to output NBT file {}. Nothing was changed",
                       output))?;
        let mut f = BufWriter::new(f);

        write::write_file(&mut f, &new_nbt).chain_err(
            || format!("Error writing NBT file {}. State of NBT file is unknown, consider restoring it from a backup.",
                       output))?;
    }

    eprintln!("File edited successfully.");
    Ok(0)
}

/// Open the user's $EDITOR on the temporary file, wait until the editor is
/// closed again, read the temporary file and attempt to parse it into NBT,
/// returning the result.
fn open_editor(tmp_path: &Path) -> Result<data::NBTFile> {
    let editor = match env::var("EDITOR") {
        Ok(x) => x,
        Err(_) => {
            match env::var("VISUAL") {
                Ok(x) => x,
                Err(_) => bail!("Unable to find $EDITOR"),
            }
        },
    };

    let mut cmd = Command::new(editor);
    let _: &mut Command = cmd.arg(&tmp_path.as_os_str());
    let mut cmd = cmd.spawn().chain_err(|| "Error opening editor")?;

    match cmd.wait() {
        Ok(x) if x.success() => (),
        Ok(_) => bail!("Editor did not exit correctly"),
        Err(e) => {
            return Err(Error::with_chain(e, "Error executing editor"));
        },
    }

    /* Then we parse the text format in the temporary file into NBT */
    let mut f = File::open(&tmp_path).chain_err(
        || "Unable to read temporary file. Nothing was changed.")?;

    string_read::read_file(&mut f).map_err(|e| e.into())
}

/// When the user wants to print an NBT file to text format
fn print(input: &str, output: &str) -> Result<i32> {
    /* First we read a NBTFile from the input */
    let nbt = if input == "-" {
        let f = io::stdin();
        let mut f = f.lock();
        read::read_file(&mut f).chain_err(
            || format!("Unable to parse {}, are you sure it's an NBT file?",
                       input))?
    } else {
        let path: &Path = Path::new(input);
        let f = File::open(path).chain_err(
            || format!("Unable to open file {}", input))?;
        let mut f = BufReader::new(f);

        read::read_file(&mut f).chain_err(
            || format!("Unable to parse {}, are you sure it's an NBT file?",
                       input))?
    };

    /* Then we write the NBTFile to the output in text format */
    if output == "-" {
        let f = io::stdout();
        let mut f = f.lock();
        /* If we get an error writing to stdout, we want to just silently exit
         * with exit code 1. (It can generally be assumed that nbted will not
         * error in serializing the data, so any error here would be because of
         * writing to stdout) */
        match string_write::write_file(&mut f, &nbt) {
            Ok(()) => (),
            Err(_) => return Ok(1),
        }
    } else {
        let path: &Path = Path::new(output);
        let f = File::create(&path).chain_err(
            || format!("Unable to write to output NBT file {}. Nothing was changed.",
                       output))?;
        let mut f = BufWriter::new(f);

        string_write::write_file(&mut f, &nbt).chain_err(
            || format!("Error writing NBT file {}. State of NBT file is unknown, consider restoring it from a backup.",
                       output))?;
    }

    Ok(0)
}

/// When the user wants to convert a text format file into an NBT file
///
/// Returns an integer representing the program's exit status.
fn reverse(input: &str, output: &str) -> Result<i32> {
    /* First we read the input file in the text format */
    let path: &Path = Path::new(input);
    let mut f = File::open(&path).chain_err(
        || format!("Unable to read text file {}",
                   input))?;

    let nbt = string_read::read_file(&mut f).chain_err(
        || format!("Unable to parse text file {}",
        input))?;

    /* Then we write the parsed NBT to the output file in NBT format */
    if output == "-" {
        let f = io::stdout();
        let mut f = f.lock();
        /* If we get an error writing to stdout, we want to just silently exit
         * with exit code 1. (It can generally be assumed that nbted will not
         * error in serializing the data, so any error here would be because of
         * writing to stdout) */
        match write::write_file(&mut f, &nbt) {
            Ok(()) => (),
            Err(_) => return Ok(1),
        }
    } else {
        let path: &Path = Path::new(output);
        let f = File::create(&path).chain_err(
            || format!("Unable to write to output NBT file {}. Nothing was changed",
                       output))?;
        let mut f = BufWriter::new(f);

        write::write_file(&mut f, &nbt).chain_err(
            || format!("error writing to NBT FILE {}, state of NBT file is unknown, consider restoring it from a backup.",
                       output))?;
    }

    Ok(0)
}
<|endoftext|>


<commit_before>#![feature(globs)]

extern crate ncurses;

use ncurses::*;

/*
 * So, how this working?
 * And what functions do we need?
 * First of all, we need 2 fields (user and enemy)
 * So, list of functions
 * initialize, deinit
 * init_field()
 * get_input() -> i32;
 *
 */

enum CellType {
    EMPTY = 0, // Just empty space
    SHOT = 1, // o ?
    SHIP = 2, // x ?
}

enum Color {
    GREEN = 16, // For numbers and current ship while placing
    PINK = 17, // For shoted ships
    RED = 18, // For death ships
    WHITE = 19, // For shot in empty space
}

struct Cell {
    Type: CellType,
    Color: Color
}

impl Cell {
   fn print(&self) {
       let mut symbol = " ";
       match self.Type {
           EMPTY => symbol = ".",
           SHOT => symbol = "o",
           SHIP => symbol = "x",
       }
       printw(format!("{}", symbol).as_slice());
   }
}

struct Coord {
    y: uint,
    x: uint
}

struct Ship {
    coord: Coord,
    cells: Vec<Coord>
}

fn init_colors() {
    start_color();
    init_color((Color::GREEN as i16), 0, 150 * 4, 0);
    init_color((Color::PINK as i16), 0, 150 * 4, 0);
    init_color((Color::RED as i16), 0, 150 * 4, 0);
    init_color((Color::WHITE as i16), 0, 150 * 4, 0);
}

fn initialize() {
    initscr();

    curs_set(CURSOR_INVISIBLE);

    init_colors();

    keypad(stdscr, true);
    noecho();
}

fn deinitialize() {
    endwin();
}

fn get_input() -> i32 {
    getch()
}

fn print_field(field: [[Cell, ..10], ..10], y: i32, x: i32) {
    mv(y, x+2i32);
    for i in range(1u, 11u) {
        printw(format!(" {}", i).as_slice());
    }
    for i in range(1u, 11u) {
        if i != 10u {
            mv(y+(i as i32), x+1i32);
        }
        else {
            mv(y+(i as i32), x);
        }
        printw(format!("{}", i).as_slice());
    }
    for i in range(0u, 10u) {
        mv(y+(i as i32)+1i32, x+2i32);
        for j in range(0u, 10u) {
            printw(" ");
            field[i][j].print();
        }
        printw("\n");
    }
}

fn add_ship(field: &mut [[Cell, ..10], ..10], ship: &Ship) {
    let y = ship.coord.y;
    let x = ship.coord.x;
    for i in ship.cells.iter() {
        let cy = i.y;
        let cx = i.x;
        field[y+cy][x+cx] = Cell { Type : SHIP, Color : GREEN };
    }
}

fn remove_ship(field: &mut [[Cell, ..10], ..10], ship: &Ship) {
    let y = ship.coord.y;
    let x = ship.coord.x;
    for i in ship.cells.iter() {
        let cy = i.y;
        let cx = i.x;
        field[y+cy][x+cx] = Cell { Type : EMPTY, Color : WHITE };
    }
}

fn rotate_ship(ship: &mut Ship) {
    for i in range(0u, ship.cells.len()) {
        let temp = ship.cells[i].y;
        ship.cells[i].y = ship.cells[i].x;
        ship.cells[i].x = temp;
    }
}

fn move_ship(field: &mut [[Cell, ..10], ..10], ship: &mut Ship, input: i32) -> bool {
    remove_ship(field, &*ship);
    let mut x = ship.coord.x;
    let mut y = ship.coord.y;
    match input {
        KEY_LEFT =>  {
            if field[y][x-1].Type == SHIP {
                return false;
            }
            ship.coord.x-=1;
        }
        KEY_UP =>  {
            if field[y-1][x].Type == SHIP {
                return false;
            }
            ship.coord.y-=1;
        }
        KEY_DOWN =>  {
            if field[y+1][x].Type == SHIP {
                return false;
            }
            ship.coord.y+=1;
        }
        KEY_RIGHT =>  {
            if field[y][x+1].Type == SHIP {
                return false;
            }
            ship.coord.x+=1;
        }
        KEY_ENTER => {
            add_ship(field, &*ship);
            return true;
        }
        _ => {
            rotate_ship(ship);
        }
    }
    add_ship(field, &*ship);
    return false;
}

fn main() {
    initialize();

    let mut userfield = [[Cell { Type : EMPTY, Color : WHITE }, ..10], ..10];
    let mut enemyfield = [[Cell { Type : EMPTY, Color : WHITE }, ..10], ..10];

    let oneship : Ship = Ship {
        coord : Coord {y : 0u, x : 0u},
        cells : vec![ Coord {y : 0u, x : 0u}]
    };

    let twoship : Ship = Ship {
        coord : Coord {y : 0u, x : 0u},
        cells : vec![ Coord {y : 0u, x : 0u},
                      Coord {y : 1u, x : 0u}]
    };

    let fourship : Ship = Ship {
        coord : Coord {y : 1u, x : 0u},
        cells : vec![ Coord {y : -1u, x : 0u},
                      Coord {y : 0u, x : 0u},
                      Coord {y : 1u, x : 0u},
                      Coord {y : 2u, x : 0u}]
    };

    // Which ships must be placed?
    // 1 - 4ship
    // 2 - 3ship
    // 3 - 2ship
    // 4 - 1ship
    // sum - 10

    let mut count = 0u;
    let mut ch = getch();
    let mut curShip : Ship = fourship;
    while ch != ('q' as i32) {
        if (move_ship(&mut userfield, &mut curShip, ch)) {
            count+=1;
            if count <= 2 {
                curShip = Ship {
                    coord : Coord {y : 1u, x : 0u},
                    cells : vec![ Coord {y : -1u, x : 0u},
                                  Coord {y : 0u, x : 0u},
                                  Coord {y : 1u, x : 0u}]
                };
            }
            else if count <= 5 {
                curShip = Ship {
                    coord : Coord {y : 0u, x : 0u},
                    cells : vec![ Coord {y : 0u, x : 0u},
                                  Coord {y : 1u, x : 0u}]
                };
            }
            else if count <= 9 {
                curShip = Ship {
                    coord : Coord {y : 0u, x : 0u},
                    cells : vec![ Coord {y : 0u, x : 0u}]
                };
            }
        }

        print_field(userfield, 0, 0);
        print_field(enemyfield, 0, 35);
        refresh();
        ch = getch();
    }

    // need on end of each frame?

    deinitialize();
}
<commit_msg>A lot of changes, implented a lot of features<commit_after>#![feature(globs)]

extern crate ncurses;

use ncurses::*;
use std::rand;

enum CellType {
    EMPTY = 0,
    SHOT = 1,
    SHIP = 2,
    COLLISION_SHIP = 3,
    HIDE_SHIP = 4,
    SHOT_POS = 5,
}

enum Color {
    BACKGROUND = 15,
    GREEN = 16,
    PINK = 17,
    RED = 18,
    WHITE = 19,
    BLUE = 20,
}

enum Status {
    START = 0,
    PLACE_SHIP = 1,
    PLAYER_TURN = 2,
    AI_TURN = 3,
    QUIT = 4,
}

static COLOR_PAIR_NUMBER: i16 = 1;
static COLOR_PAIR_EMPTY: i16 = 2;
static COLOR_PAIR_SHIP: i16 = 3;
static COLOR_PAIR_COLLISION_SHIP: i16 = 4;
static COLOR_PAIR_SHOT: i16 = 5;
static COLOR_PAIR_SHOT_POS: i16 = 6;

static DEBUG: bool = false;

struct Cell {
    Type: CellType,
    Color: Color
}

impl Cell {
   fn print(&self) {
       let mut symbol = " ";
       match self.Type {
           HIDE_SHIP => {
               if DEBUG {
               attron(COLOR_PAIR(COLOR_PAIR_SHIP));
               printw(".");
               attroff(COLOR_PAIR(COLOR_PAIR_SHIP));
               }
               else {
               attron(COLOR_PAIR(COLOR_PAIR_EMPTY));
               printw(".");
               attroff(COLOR_PAIR(COLOR_PAIR_EMPTY));
               }
           }
           EMPTY => {
               attron(COLOR_PAIR(COLOR_PAIR_EMPTY));
               printw(".");
               attroff(COLOR_PAIR(COLOR_PAIR_EMPTY));
           }
           SHOT => {
               attron(COLOR_PAIR(COLOR_PAIR_SHOT));
               printw("o");
               attroff(COLOR_PAIR(COLOR_PAIR_SHOT));
           }
           SHIP => {
               attron(COLOR_PAIR(COLOR_PAIR_SHIP));
               printw("x");
               attroff(COLOR_PAIR(COLOR_PAIR_SHIP));
           }
           COLLISION_SHIP => {
               attron(COLOR_PAIR(COLOR_PAIR_COLLISION_SHIP));
               printw("x");
               attroff(COLOR_PAIR(COLOR_PAIR_COLLISION_SHIP));
           }
           SHOT_POS => {
               attron(COLOR_PAIR(COLOR_PAIR_SHOT_POS));
               printw("o");
               attroff(COLOR_PAIR(COLOR_PAIR_SHOT_POS));
           }
       }
   }
}

type Field = [[Cell, ..10], ..10];

struct Coord {
    y: uint,
    x: uint
}

struct ShipCell {
    coord: Coord,
    cell: CellType
}

struct Ship {
    coord: Coord,
    cells: Vec<ShipCell>,
    can_be_placed: bool
}

fn init_colors() {
    start_color();

    init_color((Color::BACKGROUND as i16), 27 * 4, 29 * 4, 32 * 4);
    init_color((Color::GREEN as i16), 135 * 4, 140 * 4, 73 * 4);
    init_color((Color::PINK as i16), 127, 58 * 4, 96 * 4);
    init_color((Color::RED as i16), 130 * 4, 68 * 4, 76 * 4);
    init_color((Color::WHITE as i16), 51 * 4, 95 * 4, 121 * 4);
    init_color((Color::BLUE as i16), 51 * 4, 95 * 4, 121 * 4);

    init_pair(COLOR_PAIR_NUMBER, Color::GREEN as i16, Color::BACKGROUND as i16);
    init_pair(COLOR_PAIR_EMPTY, Color::WHITE as i16, Color::BACKGROUND as i16);
    init_pair(COLOR_PAIR_SHIP, Color::GREEN as i16, Color::BACKGROUND as i16);
    init_pair(COLOR_PAIR_SHOT, Color::PINK as i16, Color::BACKGROUND as i16);
    init_pair(COLOR_PAIR_SHOT_POS, Color::BLUE as i16, Color::BACKGROUND as i16);
    init_pair(COLOR_PAIR_COLLISION_SHIP, Color::RED as i16, Color::BACKGROUND as i16);

    bkgd(' ' as u32 | COLOR_PAIR(COLOR_PAIR_EMPTY) as u32);
}

fn initialize() {
    initscr();

    curs_set(CURSOR_INVISIBLE);

    init_colors();

    keypad(stdscr, true);
    noecho();
}

fn deinitialize() {
    endwin();
}

fn get_input() -> i32 {
    getch()
}

fn print_field(field: Field, y: i32, x: i32) {
    mv(y, x+2i32);
    for i in range(1u, 11u) {
        attron(COLOR_PAIR(COLOR_PAIR_NUMBER));
        printw(format!(" {}", i).as_slice());
        attroff(COLOR_PAIR(COLOR_PAIR_NUMBER));
    }
    for i in range(1u, 11u) {
        if i != 10u {
            mv(y+(i as i32), x+1i32);
        }
        else {
            mv(y+(i as i32), x);
        }
        attron(COLOR_PAIR(COLOR_PAIR_NUMBER));
        printw(format!("{}", i).as_slice());
        attroff(COLOR_PAIR(COLOR_PAIR_NUMBER));
    }
    for i in range(0u, 10u) {
        mv(y+(i as i32)+1i32, x+2i32);
        for j in range(0u, 10u) {
            printw(" ");
            field[i][j].print();
        }
        printw("\n");
    }
}

fn add_ship(field: &mut Field, ship: &Ship) {
    let y = ship.coord.y;
    let x = ship.coord.x;
    for i in ship.cells.iter() {
        let cy = i.coord.y;
        let cx = i.coord.x;
        field[y+cy][x+cx] = Cell { Type : i.cell, Color : GREEN };
    }
}

fn remove_ship(field: &mut Field, ship: &Ship) {
    let y = ship.coord.y;
    let x = ship.coord.x;
    for i in ship.cells.iter() {
        let cy = i.coord.y;
        let cx = i.coord.x;
        field[y+cy][x+cx] = Cell { Type : EMPTY, Color : WHITE };
    }
}

fn rotate_ship(ship: &mut Ship) {
    for i in range(0u, ship.cells.len()) {
        let temp = ship.cells[i].coord.y;
        ship.cells[i].coord.y = ship.cells[i].coord.x;
        ship.cells[i].coord.x = temp;
    }
}

fn have_neighbours(field: Field, y: uint, x: uint) -> bool {
    let mx : int = x as int+1;
    let lx : int = x as int-1;
    let my : int = y as int+1;
    let ly : int = y as int-1;

    if field[y][x].Type as int == SHIP as int { return true; }
    if (lx >= 0) && (field[y][x-1].Type as int == SHIP as int) { return true; }
    if (mx < 10) && (field[y][x+1].Type as int == SHIP as int) { return true; }
    if (ly >= 0) && (field[y-1][x].Type as int == SHIP as int) { return true; }
    if (my < 10) && (field[y+1][x].Type as int == SHIP as int) { return true; }
    if (ly >= 0) && (lx >= 0) && (field[y-1][x-1].Type as int == SHIP as int)  { return true; }
    if (ly >= 0) && (mx < 10) && (field[y-1][x+1].Type as int == SHIP as int)  { return true; }
    if (my < 10) && (lx >= 0) && (field[y+1][x-1].Type as int == SHIP as int)  { return true; }
    if (my < 10) && (mx < 10) && (field[y+1][x+1].Type as int == SHIP as int)  { return true; }

    return false;
}

fn collision(field: &Field, ship: &mut Ship) -> bool {
    let y = ship.coord.y;
    let x = ship.coord.x;
    let mut canbeplaced = true;
    for i in range(0u, ship.cells.len()) {
        let cy = ship.cells[i].coord.y;
        let cx = ship.cells[i].coord.x;
        if (y+cy == -1) || (y+cy == 10) || (x+cx == -1) || (x+cx == 10) {
            return true;
        }

        // Check not only y+cy;x+cx, but and around this cell
        if have_neighbours(*field, y+cy, x+cx) {
            ship.can_be_placed = false;
            canbeplaced = false;
            ship.cells[i].cell = COLLISION_SHIP;
        }
        else if field[y+cy][x+cx].Type as int == EMPTY as int {
            ship.cells[i].cell = SHIP;
        }
    }

    if (canbeplaced) {
        ship.can_be_placed = true;
    }

    return false;
}

fn move_ship(field: &mut Field, ship: &mut Ship, input: i32) -> bool {
    let mut x = ship.coord.x;
    let mut y = ship.coord.y;

    match input {
        KEY_LEFT =>  {
            ship.coord.x-=1;
            if collision(&*field, ship) {
                ship.coord.x+=1;
            }
        }
        KEY_UP =>  {
            ship.coord.y-=1;
            if collision(&*field, ship) {
                ship.coord.y+=1;
            }
        }
        KEY_DOWN =>  {
            ship.coord.y+=1;
            if collision(&*field, ship) {
                ship.coord.y-=1;
            }
        }
        KEY_RIGHT =>  {
            ship.coord.x+=1;
            if collision(&*field, ship) {
                ship.coord.x-=1;
            }
        }
        KEY_F1 => {
            if (ship.can_be_placed) {
                return true;
            }
        }
        _ => {
            rotate_ship(ship);
            if collision(&*field, ship) {
                rotate_ship(ship);
            }
        }
    }
    return false;
}

fn remember_before(field: Field, ship: &Ship, before: &mut Vec<ShipCell>) {
    before.clear();
    let cury = ship.coord.y;
    let curx = ship.coord.x;
    for i in ship.cells.iter() {
        let y = cury + i.coord.y;
        let x = curx + i.coord.x;
        before.push(ShipCell { coord : Coord { y: y, x: x}, cell : field[y][x].Type });
    }
}

fn place_before(field: &mut Field, before: &Vec<ShipCell>) {
    for i in before.iter() {
        field[i.coord.y][i.coord.x].Type = i.cell;
    }
}

fn print_menu() {
    clear();
    let mut height : i32 = 0;
    let mut width : i32 = 0;
    getmaxyx(stdscr, &mut height, &mut width);
    mv(height/2-8, width/2-23);
    printw("______       _   _   _           _     _       \n");
    mv(height/2-7, width/2-23);
    printw("| ___ \\     | | | | | |         | |   (_)      \n");
    mv(height/2-6, width/2-23);
    printw("| |_/ / __ _| |_| |_| | ___  ___| |__  _ _ __  \n");
    mv(height/2-5, width/2-23);
    printw("| ___ \\/ _` | __| __| |/ _ \\/ __| '_ \\| | '_ \\ \n");
    mv(height/2-4, width/2-23);
    printw("| |_/ / (_| | |_| |_| |  __/\\__ \\ | | | | |_) |\n");
    mv(height/2-3, width/2-23);
    printw("\\____/ \\__,_|\\__|\\__|_|\\___||___/_| |_|_| .__/ \n");
    mv(height/2+3, width/2-8);
    printw("<F1>: Start game\n");
    mv(height/2+4, width/2-5);
    printw("<F2>: About\n");
    mv(height/2+5, width/2-5);
    printw("<Q>: Quit\n");
}

fn print_about() {
    clear();
    let mut height : i32 = 0;
    let mut width : i32 = 0;
    getmaxyx(stdscr, &mut height, &mut width);
    mv(height/2-2, width/2-10);
    printw("Battleship on rust.\n");
    mv(height/2, width/2-23);
    printw("Sources: https://github.com/queyenth/battleship\n");
}

fn ai_place_ship(field : &mut Field) {
    let mut curShip : Ship = Ship {
        coord : Coord {y : 0u, x : 1u},
        can_be_placed: true,
        cells : vec![ ShipCell { coord : Coord {y : -1u, x : 0u}, cell : SHIP },
                      ShipCell { coord : Coord {y : 0u, x : 0u}, cell : SHIP},
                      ShipCell { coord : Coord {y : 1u, x : 0u}, cell : SHIP},
                      ShipCell { coord : Coord {y : 2u, x : 0u}, cell : SHIP}]
    };
    let mut count : uint = 0u;
    while count != 10 {
        let x = (rand::random::<uint>() % 10u);
        let y = (rand::random::<uint>() % 10u);
        let r = (rand::random::<uint>() % 2u);
        if count == 0 {
            curShip = Ship {
                coord : Coord {y : y, x : x},
                can_be_placed: true,
                cells : vec![ ShipCell { coord : Coord {y : -1u, x : 0u}, cell : SHIP },
                              ShipCell { coord : Coord {y : 0u, x : 0u}, cell : SHIP},
                              ShipCell { coord : Coord {y : 1u, x : 0u}, cell : SHIP},
                              ShipCell { coord : Coord {y : 2u, x : 0u}, cell : SHIP}]
            };
        }
        else if count <= 2 {
            curShip = Ship {
                coord : Coord {y : y, x : x},
                can_be_placed: true,
                cells : vec![ ShipCell { coord : Coord {y : -1u, x : 0u}, cell : SHIP},
                              ShipCell { coord : Coord {y : 0u, x : 0u}, cell : SHIP},
                              ShipCell { coord : Coord {y : 1u, x : 0u}, cell : SHIP}]
            };
        }
        else if count <= 5 {
            curShip = Ship {
                coord : Coord {y : y, x : x},
                can_be_placed: true,
                cells : vec![ ShipCell { coord : Coord {y : 0u, x : 0u}, cell : SHIP},
                              ShipCell { coord : Coord {y : 1u, x : 0u}, cell : SHIP}]
            };
        }
        else if count <= 9 {
            curShip = Ship {
                coord : Coord {y : y, x : x},
                can_be_placed: true,
                cells : vec![ ShipCell { coord : Coord {y : 0u, x : 0u}, cell : SHIP}]
            };
        }
        if r == 1 {
            rotate_ship(&mut curShip);
        }
        if collision(field, &mut curShip) {
            continue;
        }
        else if curShip.can_be_placed {
            count+=1;
            add_ship(field, &curShip);
        }
    }
}

fn hide_ships(field: &mut Field) {
    for i in range(0u, 10u) {
        for j in range(0u, 10u) {
            if field[i][j].Type as int == SHIP as int {
                field[i][j].Type = HIDE_SHIP;
            }
        }
    }
}

fn tryToShot(field: &mut Field, y: uint, x: uint) -> bool {
    if field[y][x].Type as int == HIDE_SHIP as int {
        field[y][x].Type = COLLISION_SHIP;
        return true;
    }
    return false;
}

fn main() {
    initialize();

    let mut gamestatus : Status = START;

    let mut userfield = [[Cell { Type : EMPTY, Color : WHITE }, ..10], ..10];
    let mut enemyfield = [[Cell { Type : EMPTY, Color : WHITE }, ..10], ..10];
    let mut height : i32 = 0;
    let mut width : i32 = 0;
    let mut shotPos = Coord {x : 0u, y : 0u};
    getmaxyx(stdscr, &mut height, &mut width);

    while gamestatus as int != QUIT as int {
        match gamestatus {
            START => {
                // draw menu here
                let qkey = 'q' as i32;
                while gamestatus as int != PLACE_SHIP as int {
                    print_menu();
                    let mut ch = get_input();
                    match ch {
                        KEY_F1 => {
                            gamestatus = PLACE_SHIP;
                        }
                        KEY_F2 => {
                            print_about();
                            ch = get_input();
                        }
                        _ => {}
                    }
                }
            }
            PLACE_SHIP => {
                clear();
                ai_place_ship(&mut enemyfield);
                hide_ships(&mut enemyfield);
                if DEBUG {
                    ai_place_ship(&mut userfield);
                    gamestatus = PLAYER_TURN;
                }
                else {
                let mut curShip : Ship = Ship {
                    coord : Coord {y : 1u, x : 0u},
                    can_be_placed: true,
                    cells : vec![ ShipCell { coord : Coord {y : -1u, x : 0u}, cell : SHIP },
                                  ShipCell { coord : Coord {y : 0u, x : 0u}, cell : SHIP},
                                  ShipCell { coord : Coord {y : 1u, x : 0u}, cell : SHIP},
                                  ShipCell { coord : Coord {y : 2u, x : 0u}, cell : SHIP}]
                };

                let mut before : Vec<ShipCell> = vec![];

                remember_before(userfield, &curShip, &mut before);
                add_ship(&mut userfield, &curShip);

                print_field(userfield, height/2-5, width/2-30);
                print_field(enemyfield, height/2-5, width/2+5);

                let mut count = 0u;
                let mut ch = getch();

                // TODO: rewrite this, 'count' is bad solution
                loop {
                    if ch == ('q' as i32) {
                        gamestatus = QUIT;
                        break;
                    }
                    remove_ship(&mut userfield, &curShip);
                    let shipWasPlaced = move_ship(&mut userfield, &mut curShip, ch);
                    place_before(&mut userfield, &before);
                    remember_before(userfield, &curShip, &mut before);
                    if (shipWasPlaced) {
                        add_ship(&mut userfield, &curShip);
                        count+=1;
                        if count <= 2 {
                            curShip = Ship {
                                coord : Coord {y : 1u, x : 0u},
                                can_be_placed: true,
                                cells : vec![ ShipCell { coord : Coord {y : -1u, x : 0u}, cell : SHIP},
                                              ShipCell { coord : Coord {y : 0u, x : 0u}, cell : SHIP},
                                              ShipCell { coord : Coord {y : 1u, x : 0u}, cell : SHIP}]
                            };
                            remember_before(userfield, &curShip, &mut before);
                            collision(&userfield, &mut curShip);
                            add_ship(&mut userfield, &curShip);
                        }
                        else if count <= 5 {
                            curShip = Ship {
                                coord : Coord {y : 0u, x : 0u},
                                can_be_placed: true,
                                cells : vec![ ShipCell { coord : Coord {y : 0u, x : 0u}, cell : SHIP},
                                              ShipCell { coord : Coord {y : 1u, x : 0u}, cell : SHIP}]
                            };
                            remember_before(userfield, &curShip, &mut before);
                            collision(&userfield, &mut curShip);
                            add_ship(&mut userfield, &curShip);
                        }
                        else if count <= 9 {
                            curShip = Ship {
                                coord : Coord {y : 0u, x : 0u},
                                can_be_placed: true,
                                cells : vec![ ShipCell { coord : Coord {y : 0u, x : 0u}, cell : SHIP}]
                            };
                            remember_before(userfield, &curShip, &mut before);
                            collision(&userfield, &mut curShip);
                            add_ship(&mut userfield, &curShip);
                        }
                        else {
                            gamestatus = PLAYER_TURN;
                            break;
                        }
                    }
                    else {
                        add_ship(&mut userfield, &curShip);
                    }

                    print_field(userfield, height/2-5, width/2-30);
                    print_field(enemyfield, height/2-5, width/2+5);

                    // Need refresh after each frame?
                    refresh();
                    ch = getch();
                }
                }
            }
            PLAYER_TURN => {
                clear();
                print_field(userfield, height/2-5, width/2-30);
                print_field(enemyfield, height/2-5, width/2+5);
                let mut ch = getch();
                match ch {
                    KEY_LEFT => {
                        if shotPos.x as int - 1 as int >= 0 {
                            shotPos.x-=1;
                        }
                    }
                    KEY_RIGHT => {
                        if shotPos.x as int + 1 as int <= 9 {
                            shotPos.x+=1;
                        }
                    }
                    KEY_UP => {
                        if shotPos.y as int - 1 as int >= 0 {
                            shotPos.y-=1;
                        }
                    }
                    KEY_DOWN => {
                        if shotPos.y as int + 1 as int >= 0 {
                            shotPos.y+=1;
                        }
                    }
                    KEY_F1 => {
                        if !tryToShot(&mut enemyfield, shotPos.y, shotPos.x) {
                            // gamestatus = AI_TURN;
                        }
                    }
                    _ => {}
                }
                enemyfield[shotPos.y][shotPos.x].Type = SHOT_POS;
            }
            AI_TURN => {
                clear();
                printw("Ai turn!\n");
                gamestatus = PLAYER_TURN;
            }
            _ => {}
        }
    }

    deinitialize();
}
<|endoftext|>


<commit_before>#![allow(unused_features)] // so we can still feature(os) when testing
#![feature(collections, path, io, core, os)]
extern crate "rustc-serialize" as rustc_serialize;
extern crate docopt;

use std::os;
use docopt::Docopt;

mod elem;
mod parser;
mod error;
mod database;
mod mass;

const USAGE: &'static str = "
usage:
    chemtool <formula>
    chemtool [-h | --help]
    chemtool [-v | --version]

options:
    -h --help     Display this message and then exit.
    -v --version  Display the version number and then exit.
";

const VERSION: &'static str = "chemtool 0.1.1";

#[derive(RustcDecodable)]
struct Args {
    arg_formula: String,
}

#[cfg(not(test))]
fn main() {
    let args: Args = Docopt::new(USAGE)
                            .and_then(|d| {
                                d.help(true)
                                 .version(Some(VERSION.to_string()))
                                 .decode()
                            })
                            .unwrap_or_else(|e| e.exit());
    let mut path = Path::new(&os::args()[0]);
    path.set_filename("elemdb.csv");
    let input = args.arg_formula.as_slice();
    if let Err(e) = mass::pretty_print_mass(input, &path) {
        e.print(input);
    }
}<commit_msg>Add flag to specify the database file<commit_after>#![allow(unused_features)] // so we can still feature(os) when testing
#![feature(collections, path, io, core, os)]
extern crate "rustc-serialize" as rustc_serialize;
extern crate docopt;

use std::os;
use docopt::Docopt;

mod elem;
mod parser;
mod error;
mod database;
mod mass;

const USAGE: &'static str = "
usage:
    chemtool <formula> [options]
    chemtool [-h | --help]
    chemtool [-v | --version]

options:
    -h --help       Display this message and then exit.
    -v --version    Display the version number and then exit.
    --db-path PATH  Explicitly specify the path to the database file.
";

const VERSION: &'static str = "chemtool 0.1.1";

#[derive(RustcDecodable)]
struct Args {
    arg_formula: String,
    flag_db_path: Option<String>,
}

#[cfg(not(test))]
fn main() {
    let args: Args = Docopt::new(USAGE)
                            .and_then(|d| {
                                d.help(true)
                                 .version(Some(VERSION.to_string()))
                                 .decode()
                            })
                            .unwrap_or_else(|e| e.exit());
    let path = if let Some(path) = args.flag_db_path {
        Path::new(path)
    } else {
        let mut path = Path::new(&os::args()[0]);
        path.set_filename("elemdb.csv");
        path
    };
    let input = args.arg_formula.as_slice();
    if let Err(e) = mass::pretty_print_mass(input, &path) {
        e.print(input);
    }
}<|endoftext|>


<commit_before>extern crate rustc_serialize;
extern crate docopt;
extern crate num;

use std::collections::HashSet;
use docopt::Docopt;

const USAGE: &'static str = "
rust-euler

Rust implementations of the Project Euler problems.

Usage: rust-euler [options]

Options:
	-p, --problem=K    Which Project Euler problem to solve [default: 1]
	-h, --help         Display this help and exit
";

#[derive(Debug, RustcDecodable)]
struct Args {
	flag_problem: u8,
	flag_help: bool,
}

fn main() {
	let args: Args = Docopt::new(USAGE).and_then(|d| d.decode())
									   .unwrap_or_else(|e| e.exit());
	match args.flag_problem {
		1 => problem_1(),
		2 => problem_2(),
		3 => problem_3(),
		_ => println!("Problem number solution not supported.")
	}
}

fn problem_1() {
	let mut multiples = HashSet::new();
	for i in num::range_step(3, 1000, 3) {
		multiples.insert(i);
	}
	for i in num::range_step(5, 1000, 5) {
		multiples.insert(i);
	}
	let sum = multiples.iter().fold(0, |sum, x| sum + x);
	println!("{}", sum);
}

fn problem_2() {
	let mut current = 2;
	let mut previous = 1;
	let mut n = 0;
	let mut sum = 2;
	while current < 4000000 {
		let temp = current;
		current = temp + previous;
		previous = temp;
		n = n + 1;
		if n == 3 {
			sum += current;
			n = 0;
		}
	}
	println!("{}", sum)
}

fn is_prime(x: u64) -> bool {
	let mut i = 2;
	while i < (x / 2) {
		if x % i == 0 {
			return false;
		}
		i += 1;
	}
	return true;
}

fn problem_3() {
	 let a : u64 = 600851475143;
	 let mut d = 2;
	 while d < (a / 2) {
	 	// Check that the result is an integer
	 	if a % d == 0 {
	 		match is_prime(a / d) {
	 			true => {println!("{}", a/d); return;},
	 			false => {}
	 		}
	 	}
	 	d += 1;
	 }
}<commit_msg>Altered function return types from void to (u64, String)<commit_after>extern crate rustc_serialize;
extern crate docopt;
extern crate num;

use std::collections::HashSet;
use docopt::Docopt;

const USAGE: &'static str = "
rust-euler

Rust implementations of the Project Euler problems.

Usage: rust-euler [options]

Options:
	-p, --problem=K    Which Project Euler problem to solve [default: 1]
	-h, --help         Display this help and exit
";

#[derive(Debug, RustcDecodable)]
struct Args {
	flag_problem: u8,
	flag_help: bool,
}

fn main() {
	let args: Args = Docopt::new(USAGE).and_then(|d| d.decode())
									   .unwrap_or_else(|e| e.exit());
	let mut problem : fn() -> (u64, String);
	match args.flag_problem {
		1 => problem = problem_1,
		2 => problem = problem_2,
		3 => problem = problem_3,
		_ => problem = problem_not_supported,
	}
	let (result, message) = problem();
	println!("{}", message);
	println!("{}", result);
}

fn problem_1() -> (u64, String) {
	let mut multiples = HashSet::new();
	for i in num::range_step(3, 1000, 3) {
		multiples.insert(i);
	}
	for i in num::range_step(5, 1000, 5) {
		multiples.insert(i);
	}
	let sum = multiples.iter().fold(0, |sum, x| sum + x);
	return (sum, "".to_string());
}

fn problem_2() -> (u64, String) {
	let mut current = 2;
	let mut previous = 1;
	let mut n = 0;
	let mut sum = 2;
	while current < 4000000 {
		let temp = current;
		current = temp + previous;
		previous = temp;
		n = n + 1;
		if n == 3 {
			sum += current;
			n = 0;
		}
	}
	return (sum, "".to_string());
}

/* A number is prime if it is divisble by no numbers other than 2 or itself.
   Our naive prime checking function performs this check.
   However we know that if a number x is not divisble by a number i
   it is not divisible by any multiples of i.
   So really the check to see if a number is prime is to divide by all primes
   less than that number.

*/

fn is_prime(x: u64) -> bool {
	let mut i = 2;
	while i < (x / 2) {
		if x % i == 0 {
			return false;
		}
		i += 1;
	}
	return true;
}

fn problem_3() -> (u64, String) {
	 let a : u64 = 600851475143;
	 let mut d = 2;
	 while d < (a / 2) {
	 	// Check that the result is an integer
	 	if a % d == 0 {
	 		match is_prime(a / d) {
	 			true => {return (a / d, "".to_string());},
	 			false => {}
	 		}
	 	}
	 	d += 1;
	 }
	 return (a, "Failed to find any prime factors - the number must be prime.".to_string())
}

fn problem_not_supported() -> (u64, String) {
	return (0, "Problem number solution not supported".to_string());
}<|endoftext|>


<commit_before>#[macro_use]
extern crate glium;
extern crate time;

use std::mem;

use glium::{DisplayBuild, Program, Surface, VertexBuffer, IndexBuffer};
use glium::texture::Texture2d;
use glium::index::PrimitiveType;
use glium::glutin::Event;
use glium::backend::Facade;
use glium::backend::glutin_backend::GlutinFacade;

// Our vertices are boring. We only ever draw 2 triangles
// covering the whole surface.
#[derive(Copy, Clone)]
struct Vertex {
    pos: [f32; 2],
}

implement_vertex!(Vertex, pos);

fn whole_surface_triangles<F>(facade: &F)
    -> (VertexBuffer<Vertex>, IndexBuffer<u8>)
    where F: Facade,
{
    let vertices = [
        Vertex { pos: [-1.0, -1.0] },
        Vertex { pos: [-1.0,  1.0] },
        Vertex { pos: [ 1.0,  1.0] },
        Vertex { pos: [ 1.0, -1.0] },
    ];

    let indices = [
        0, 1, 2,
        2, 3, 0,
    ];

    let vertex_buffer = VertexBuffer::new(facade, &vertices).unwrap();
    let index_buffer = IndexBuffer::new(facade,
                                        PrimitiveType::TrianglesList,
                                        &indices).unwrap();

    (vertex_buffer, index_buffer)
}

// Size in pixels for the feedback textures.
const FEEDBACK_TEXTURE_SIZE: u32
    = 2048;

// Create a feedback texture.
fn feedback_texture<F>(facade: &F) -> Texture2d
    where F: Facade,
{
    // Returns a texture with undefined contents.
    Texture2d::empty(facade,
                     FEEDBACK_TEXTURE_SIZE,
                     FEEDBACK_TEXTURE_SIZE).unwrap()
}

// Shaders, as GLSL source code. These are included at compile time, so
// you don't need any files present at runtime. However it means you
// need to recompile even if you've only changed a shader.

// Trivial vertex shader.
const VERTEX_SHADER_SRC: &'static str
    = include_str!("shaders/vertex.glsl");

// Simple fragment shader, used to copy a texture to the screen.
const BLIT_SHADER_SRC: &'static str
    = include_str!("shaders/blit.glsl");

// Fragment shader which runs video feedback between two textures.
const FEEDBACK_SHADER_SRC: &'static str
    = include_str!("shaders/feedback.glsl");

// The screen and the feedback textures are interpreted as
// subsets of the complex plane:
//
//     (-SCALE to SCALE) + i*(-SCALE to SCALE)
//
const SCALE: f32 = 1.4;

// Convert a window position in pixels to a complex number.
//
// TODO: Is it slow to look up the size on every mouse move?
fn window_px_to_z(facade: &GlutinFacade, (x, y): (i32, i32))
    -> (f32, f32)
{
    let window = facade.get_window().unwrap();
    let (sx, sy) = window.get_inner_size().unwrap();

    (((x as f32) / ((sx-1) as f32) * 2.0 - 1.0) * SCALE,
     ((y as f32) / ((sy-1) as f32) * 2.0 - 1.0) * SCALE)
}

fn main() {
    // Create a glutin / glium window.
    let display = glium::glutin::WindowBuilder::new()
        .with_title("a z u r e s c e n s".to_owned())
        .with_vsync()
        .build_glium()
        .unwrap();

    // Create two textures for feedback.
    let mut read_texture  = feedback_texture(&display);
    let mut write_texture = feedback_texture(&display);

    // Prepare to draw triangles which cover a whole surface.
    let (vertex_buffer, index_buffer) = whole_surface_triangles(&display);
    let draw_params = Default::default();

    // Prepare shader programs.
    let blit_program = Program::from_source(&display,
        VERTEX_SHADER_SRC, BLIT_SHADER_SRC, None).unwrap();

    let feedback_program = Program::from_source(&display,
        VERTEX_SHADER_SRC, FEEDBACK_SHADER_SRC, None).unwrap();

    // Clear the screen.
    let mut target = display.draw();
    target.clear_color(0.0, 0.0, 0.0, 1.0);
    target.finish().unwrap();

    // Initial value for the complex parameter 'c'.
    let mut param_c = (0.3, 0.3);

    loop {
        // Run video feedback from one texture into the other.
        // We do this twice before drawing to the screen.
        // The reason is that each frame inverts colors and
        // we want to avoid a distracting blinky effect.
        for _ in 0..2 {
            {
                let uniforms = uniform! {
                    src: &read_texture,
                    scale: SCALE,
                    param_c: param_c,
                    param_t: time::precise_time_s() as f32,
                };

                let mut target = write_texture.as_surface();
                target.draw(&vertex_buffer, &index_buffer, &feedback_program,
                            &uniforms, &draw_params).unwrap();
            }

            mem::swap(&mut read_texture, &mut write_texture);
        }

        // Draw the most recently written texture to the screen.
        let uniforms = uniform! {
            src: &read_texture,
        };

        let mut target = display.draw();
        target.draw(&vertex_buffer, &index_buffer, &blit_program,
                    &uniforms, &draw_params).unwrap();
        target.finish().unwrap();

        // Handle events.
        for ev in display.poll_events() {
            match ev {
                Event::Closed => return,
                Event::MouseMoved(x, y) => {
                    // Update the parameter 'c' according to
                    // the mouse position.
                    param_c = window_px_to_z(&display, (x,y));
                }
                _ => (),
            }
        }
    }
}
<commit_msg>Print OpenGL and GLSL versions on startup<commit_after>#[macro_use]
extern crate glium;
extern crate time;

use std::mem;

use glium::{DisplayBuild, Program, Surface, VertexBuffer, IndexBuffer};
use glium::texture::Texture2d;
use glium::index::PrimitiveType;
use glium::glutin::Event;
use glium::backend::Facade;
use glium::backend::glutin_backend::GlutinFacade;

// Our vertices are boring. We only ever draw 2 triangles
// covering the whole surface.
#[derive(Copy, Clone)]
struct Vertex {
    pos: [f32; 2],
}

implement_vertex!(Vertex, pos);

fn whole_surface_triangles<F>(facade: &F)
    -> (VertexBuffer<Vertex>, IndexBuffer<u8>)
    where F: Facade,
{
    let vertices = [
        Vertex { pos: [-1.0, -1.0] },
        Vertex { pos: [-1.0,  1.0] },
        Vertex { pos: [ 1.0,  1.0] },
        Vertex { pos: [ 1.0, -1.0] },
    ];

    let indices = [
        0, 1, 2,
        2, 3, 0,
    ];

    let vertex_buffer = VertexBuffer::new(facade, &vertices).unwrap();
    let index_buffer = IndexBuffer::new(facade,
                                        PrimitiveType::TrianglesList,
                                        &indices).unwrap();

    (vertex_buffer, index_buffer)
}

// Size in pixels for the feedback textures.
const FEEDBACK_TEXTURE_SIZE: u32
    = 2048;

// Create a feedback texture.
fn feedback_texture<F>(facade: &F) -> Texture2d
    where F: Facade,
{
    // Returns a texture with undefined contents.
    Texture2d::empty(facade,
                     FEEDBACK_TEXTURE_SIZE,
                     FEEDBACK_TEXTURE_SIZE).unwrap()
}

// Shaders, as GLSL source code. These are included at compile time, so
// you don't need any files present at runtime. However it means you
// need to recompile even if you've only changed a shader.

// Trivial vertex shader.
const VERTEX_SHADER_SRC: &'static str
    = include_str!("shaders/vertex.glsl");

// Simple fragment shader, used to copy a texture to the screen.
const BLIT_SHADER_SRC: &'static str
    = include_str!("shaders/blit.glsl");

// Fragment shader which runs video feedback between two textures.
const FEEDBACK_SHADER_SRC: &'static str
    = include_str!("shaders/feedback.glsl");

// The screen and the feedback textures are interpreted as
// subsets of the complex plane:
//
//     (-SCALE to SCALE) + i*(-SCALE to SCALE)
//
const SCALE: f32 = 1.4;

// Convert a window position in pixels to a complex number.
//
// TODO: Is it slow to look up the size on every mouse move?
fn window_px_to_z(facade: &GlutinFacade, (x, y): (i32, i32))
    -> (f32, f32)
{
    let window = facade.get_window().unwrap();
    let (sx, sy) = window.get_inner_size().unwrap();

    (((x as f32) / ((sx-1) as f32) * 2.0 - 1.0) * SCALE,
     ((y as f32) / ((sy-1) as f32) * 2.0 - 1.0) * SCALE)
}

fn main() {
    // Create a glutin / glium window.
    let display = glium::glutin::WindowBuilder::new()
        .with_title("a z u r e s c e n s".to_owned())
        .with_vsync()
        .build_glium()
        .unwrap();

    println!("OpenGL {:?}", display.get_opengl_version());
    println!("GLSL   {:?}", display.get_supported_glsl_version());

    // Create two textures for feedback.
    let mut read_texture  = feedback_texture(&display);
    let mut write_texture = feedback_texture(&display);

    // Prepare to draw triangles which cover a whole surface.
    let (vertex_buffer, index_buffer) = whole_surface_triangles(&display);
    let draw_params = Default::default();

    // Prepare shader programs.
    let blit_program = Program::from_source(&display,
        VERTEX_SHADER_SRC, BLIT_SHADER_SRC, None).unwrap();

    let feedback_program = Program::from_source(&display,
        VERTEX_SHADER_SRC, FEEDBACK_SHADER_SRC, None).unwrap();

    // Clear the screen.
    let mut target = display.draw();
    target.clear_color(0.0, 0.0, 0.0, 1.0);
    target.finish().unwrap();

    // Initial value for the complex parameter 'c'.
    let mut param_c = (0.3, 0.3);

    loop {
        // Run video feedback from one texture into the other.
        // We do this twice before drawing to the screen.
        // The reason is that each frame inverts colors and
        // we want to avoid a distracting blinky effect.
        for _ in 0..2 {
            {
                let uniforms = uniform! {
                    src: &read_texture,
                    scale: SCALE,
                    param_c: param_c,
                    param_t: time::precise_time_s() as f32,
                };

                let mut target = write_texture.as_surface();
                target.draw(&vertex_buffer, &index_buffer, &feedback_program,
                            &uniforms, &draw_params).unwrap();
            }

            mem::swap(&mut read_texture, &mut write_texture);
        }

        // Draw the most recently written texture to the screen.
        let uniforms = uniform! {
            src: &read_texture,
        };

        let mut target = display.draw();
        target.draw(&vertex_buffer, &index_buffer, &blit_program,
                    &uniforms, &draw_params).unwrap();
        target.finish().unwrap();

        // Handle events.
        for ev in display.poll_events() {
            match ev {
                Event::Closed => return,
                Event::MouseMoved(x, y) => {
                    // Update the parameter 'c' according to
                    // the mouse position.
                    param_c = window_px_to_z(&display, (x,y));
                }
                _ => (),
            }
        }
    }
}
<|endoftext|>


<commit_before>// Worklog is for recording your hours.
//

extern crate chrono;
extern crate csv;
extern crate getopts;
extern crate rustc_serialize;

mod error;
mod timeclock;
mod util;

use chrono::*;
use error::WorklogError;
use getopts::Options;
use std::env;
use std::fs::OpenOptions;
use std::io::prelude::*;
use std::path::PathBuf;
use timeclock::Direction;
use timeclock::now;

#[cfg(target_family = "unix")]
static CSV_FILE_NAME: &'static str = ".worklog.csv";
#[cfg(target_family = "windows")]
static CSV_FILE_NAME: &'static str = "worklog.csv";

// TODO This belongs in a config file somewhere
const WEEKSTART: i64 = Weekday::Sat as i64;

fn print_csv_entries<R: Read>(file: R) -> Result<(), WorklogError> {
    let csv_entries = try!(timeclock::read_timesheet(file));
    for rec in csv_entries {
        println!("{}", rec);
    }
    Ok(())
}

fn print_full_summary<R: Read>(file: R) -> Result<(), WorklogError> {
    let csv_entries = try!(timeclock::read_timesheet(file));
    let records = timeclock::collect_date_records(csv_entries);

    let mut total_hours: f64 = 0.0;
    for rec in records {
        total_hours += rec.hours();
        println!("{}", rec);
    }

    println!("Total Hours: {:.2}", total_hours);
    Ok(())
}

fn print_short_summary<R: Read>(file: R,
                                since: Date<FixedOffset>)
                                -> Result<(), WorklogError> {
    let csv_entries = try!(timeclock::read_timesheet(file));
    let records = timeclock::collect_date_records(csv_entries);

    let mut total_hours: f64 = 0.0;
    for rec in records {
        if rec.date() >= since {
            total_hours += rec.hours();
            println!("{}", rec);
        }
    }

    println!("Total Hours: {:.2}", total_hours);
    Ok(())
}

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

/// Get the path for the csv data file
fn get_csv_path() -> Result<PathBuf, WorklogError> {
    let mut data_path = match env::home_dir() {
        Some(path) => path,
        None => {
            return Err(env::VarError::NotPresent).map_err(WorklogError::Env)
        }
    };

    data_path.push(CSV_FILE_NAME);
    Ok(data_path)
}

fn main0() -> Result<i8, WorklogError> {
    let args: Vec<String> = env::args().collect();
    let program = args[0].clone();

    let mut opts = Options::new();
    opts.optflagopt("i", "in", "in time", "TIME");
    opts.optflagopt("o", "out", "out time", "TIME");
    opts.optopt("m", "memo", "the memo", "MEMO");
    opts.optflag("s", "summary", "show the full summary");
    opts.optflag("e", "entries", "show all entries");
    opts.optflag("h", "help", "print this help menu");

    let matches = try!(opts.parse(&args[1..]));

    let csv_path = try!(get_csv_path());

    let mut csv_file = try!(OpenOptions::new()
        .read(true)
        .write(true)
        .create(true)
        .open(csv_path));

    if matches.opt_present("h") {
        print_usage(&program, opts);

    } else if matches.opts_present(&["i".to_owned(), "o".to_owned()]) {
        // The ridiculous array above isn't my fault.

        // Reject --in and --out if present at the same time
        if matches.opt_present("i") && matches.opt_present("o") {
            println!("--in or --out, not both");
            return Ok(1);
        }

        let dir = if matches.opt_present("i") {
            Direction::In
        } else {
            Direction::Out
        };

        let time = matches.opt_str("i")
            .or(matches.opt_str("o"))
            .or_else(|| Some("now".to_owned()))
            .unwrap();
        let time = try!(util::parse_multi_time_fmt(&time));

        let memo =
            matches.opt_str("m").or_else(|| Some("".to_owned())).unwrap();
        timeclock::mark_time(dir, time, memo, &mut csv_file);

        println!("Clocked {:#} at {}", dir, time.format("%F %I:%M %P"));

    } else if matches.opt_present("s") {
        try!(print_full_summary(&csv_file));

    } else if matches.opt_present("e") {
        try!(print_csv_entries(&csv_file));

    } else {
        let days_back = (-WEEKSTART + 7) % 7;
        let start_date = now().date() - Duration::days(days_back);
        try!(print_short_summary(&csv_file, start_date));
    }

    Ok(0)
}

fn main() {
    match main0() {
        Ok(_) => {}
        Err(err) => println!("Whoops! {}", err),
    };
}
<commit_msg>Write errors to stderr<commit_after>// Worklog is for recording your hours.
//

extern crate chrono;
extern crate csv;
extern crate getopts;
extern crate rustc_serialize;

mod error;
mod timeclock;
mod util;

use chrono::*;
use error::WorklogError;
use getopts::Options;
use std::env;
use std::fs::OpenOptions;
use std::io::prelude::*;
use std::path::PathBuf;
use timeclock::Direction;
use timeclock::now;

#[cfg(target_family = "unix")]
static CSV_FILE_NAME: &'static str = ".worklog.csv";
#[cfg(target_family = "windows")]
static CSV_FILE_NAME: &'static str = "worklog.csv";

// TODO This belongs in a config file somewhere
const WEEKSTART: i64 = Weekday::Sat as i64;

fn print_csv_entries<R: Read>(file: R) -> Result<(), WorklogError> {
    let csv_entries = try!(timeclock::read_timesheet(file));
    for rec in csv_entries {
        println!("{}", rec);
    }
    Ok(())
}

fn print_full_summary<R: Read>(file: R) -> Result<(), WorklogError> {
    let csv_entries = try!(timeclock::read_timesheet(file));
    let records = timeclock::collect_date_records(csv_entries);

    let mut total_hours: f64 = 0.0;
    for rec in records {
        total_hours += rec.hours();
        println!("{}", rec);
    }

    println!("Total Hours: {:.2}", total_hours);
    Ok(())
}

fn print_short_summary<R: Read>(file: R,
                                since: Date<FixedOffset>)
                                -> Result<(), WorklogError> {
    let csv_entries = try!(timeclock::read_timesheet(file));
    let records = timeclock::collect_date_records(csv_entries);

    let mut total_hours: f64 = 0.0;
    for rec in records {
        if rec.date() >= since {
            total_hours += rec.hours();
            println!("{}", rec);
        }
    }

    println!("Total Hours: {:.2}", total_hours);
    Ok(())
}

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

/// Get the path for the csv data file
fn get_csv_path() -> Result<PathBuf, WorklogError> {
    let mut data_path = match env::home_dir() {
        Some(path) => path,
        None => {
            return Err(env::VarError::NotPresent).map_err(WorklogError::Env)
        }
    };

    data_path.push(CSV_FILE_NAME);
    Ok(data_path)
}

fn main0() -> Result<i8, WorklogError> {
    let args: Vec<String> = env::args().collect();
    let program = args[0].clone();

    let mut opts = Options::new();
    opts.optflagopt("i", "in", "in time", "TIME");
    opts.optflagopt("o", "out", "out time", "TIME");
    opts.optopt("m", "memo", "the memo", "MEMO");
    opts.optflag("s", "summary", "show the full summary");
    opts.optflag("e", "entries", "show all entries");
    opts.optflag("h", "help", "print this help menu");

    let matches = try!(opts.parse(&args[1..]));

    let csv_path = try!(get_csv_path());

    let mut csv_file = try!(OpenOptions::new()
        .read(true)
        .write(true)
        .create(true)
        .open(csv_path));

    if matches.opt_present("h") {
        print_usage(&program, opts);

    } else if matches.opts_present(&["i".to_owned(), "o".to_owned()]) {
        // The ridiculous array above isn't my fault.

        // Reject --in and --out if present at the same time
        if matches.opt_present("i") && matches.opt_present("o") {
            println!("--in or --out, not both");
            return Ok(1);
        }

        let dir = if matches.opt_present("i") {
            Direction::In
        } else {
            Direction::Out
        };

        let time = matches.opt_str("i")
            .or(matches.opt_str("o"))
            .or_else(|| Some("now".to_owned()))
            .unwrap();
        let time = try!(util::parse_multi_time_fmt(&time));

        let memo =
            matches.opt_str("m").or_else(|| Some("".to_owned())).unwrap();
        timeclock::mark_time(dir, time, memo, &mut csv_file);

        println!("Clocked {:#} at {}", dir, time.format("%F %I:%M %P"));

    } else if matches.opt_present("s") {
        try!(print_full_summary(&csv_file));

    } else if matches.opt_present("e") {
        try!(print_csv_entries(&csv_file));

    } else {
        let days_back = (-WEEKSTART + 7) % 7;
        let start_date = now().date() - Duration::days(days_back);
        try!(print_short_summary(&csv_file, start_date));
    }

    Ok(0)
}

fn main() {
    match main0() {
        Ok(_) => {}
        Err(err) => {
            let _ = writeln!(&mut std::io::stderr(), "Error: {}", err);
        }
    };
}
<|endoftext|>


<commit_before>// Worklog is for recording your hours.
//
extern crate chrono;
extern crate csv;
extern crate clap;
extern crate rustc_serialize;

mod error;
mod timeclock;
mod util;
mod parsers;

use chrono::*;
use clap::{Arg, ArgGroup, App};
use error::WorklogError;
use std::env;
use std::fs::OpenOptions;
use std::io::prelude::*;
use std::path::PathBuf;
use timeclock::Direction;
use timeclock::now;

#[cfg(target_family = "unix")]
static CSV_FILE_NAME: &'static str = ".worklog.csv";
#[cfg(target_family = "windows")]
static CSV_FILE_NAME: &'static str = "worklog.csv";

// default rounding mode
static ROUNDING_DEFAULT: &'static str = "+15m";

// TODO This belongs in a config file somewhere
const WEEKSTART: i64 = Weekday::Sat as i64;

fn print_csv_entries<R: Read>(file: R) -> Result<(), WorklogError> {
    let csv_entries = try!(timeclock::read_timesheet(file));
    for rec in csv_entries {
        println!("{}", rec);
    }
    Ok(())
}

fn print_full_summary<R: Read>(file: R,
                               rounding: util::Rounding)
                               -> Result<(), WorklogError> {
    let csv_entries = try!(timeclock::read_timesheet(file));
    let records = timeclock::collect_date_records(csv_entries);

    let mut total_hours: f64 = 0.0;
    for rec in records {
        let hours = util::round(rec.seconds(), rounding) / 3600.0;
        total_hours += hours;
        println!("{} {:.2} {}", rec.date().format("%F"), hours, rec.memo());
    }

    println!("Total Hours: {:.2}", total_hours);
    Ok(())
}

fn print_short_summary<R: Read>(file: R,
                                start_date: Date<FixedOffset>,
                                end_date: Date<FixedOffset>,
                                rounding: util::Rounding)
                                -> Result<(), WorklogError> {
    let csv_entries = try!(timeclock::read_timesheet(file));
    let records = timeclock::collect_date_records(csv_entries);

    let mut total_hours: f64 = 0.0;
    for rec in records {
        if start_date <= rec.date() && rec.date() <= end_date {
            let hours = util::round(rec.seconds(), rounding) / 3600.0;
            total_hours += hours;
            println!("{} {:.2} {}", rec.date().format("%F"), hours, rec.memo());
        }
    }

    println!("Total Hours: {:.2}", total_hours);
    Ok(())
}

/// Get the path for the csv data file
fn get_csv_path() -> Result<PathBuf, WorklogError> {
    let mut data_path = match env::home_dir() {
        Some(path) => path,
        None => {
            return Err(env::VarError::NotPresent).map_err(WorklogError::Env)
        }
    };

    data_path.push(CSV_FILE_NAME);
    Ok(data_path)
}

fn main0() -> Result<(), WorklogError> {
    // Using std env macro rather than depending on clap's. No difference
    // as far as I can tell.
    // The current options aren't final and will definately change again soon
    let matches = App::new(env!("CARGO_PKG_NAME"))
        .version(env!("CARGO_PKG_VERSION"))
        .author(env!("CARGO_PKG_AUTHORS"))
        .about(env!("CARGO_PKG_DESCRIPTION"))
        .arg(Arg::from_usage("[in] -i, --in 'Record an In entry'"))
        .arg(Arg::from_usage("[out] -o, --out 'Record an Out entry'"))
        .arg(Arg::from_usage("[time] -t, --time <TIME> 'time'")
            .allow_hyphen_values(true)
            .requires("inout"))
        .arg(Arg::from_usage("[memo] -m, --memo <MEMO> 'Memo for the entry'")
            .requires("inout"))
        .arg(Arg::from_usage("[summary] -s, --summary 'Print a summary'")
            .conflicts_with("inout"))
        .arg(Arg::from_usage("[log] -l, --log 'Print the full log'")
            .conflicts_with("summary")
            .conflicts_with("inout"))
        .arg(Arg::from_usage("[round] -r, --round-up 'Round totals up to the next quarter hour'")
            .conflicts_with("log")
            .conflicts_with("inout"))
        .arg(Arg::from_usage("[round_ex] -R, --round <ROUNDING> 'Round totals up, down, half'")
            .allow_hyphen_values(true)
            .conflicts_with("log")
            .conflicts_with("inout"))
        .arg(Arg::from_usage("[range] --range <TIME> <TIME> 'range'")
            .conflicts_with("log")
            .conflicts_with("inout"))
        .group(ArgGroup::with_name("inout").args(&["in", "out"]))
        .get_matches();

    let csv_path = try!(get_csv_path());

    let mut csv_file = try!(OpenOptions::new()
        .read(true)
        .write(true)
        .create(true)
        .open(csv_path));

    let rounding = {
        if matches.occurrences_of("round") > 0 {
            try!(parsers::parse_rounding(ROUNDING_DEFAULT))
        } else if matches.occurrences_of("round_ex") > 0 {
            try!(parsers::parse_rounding(matches.value_of("round_ex").unwrap()))
        } else {
            util::Rounding::None
        }
    };

    let ctime = now();

    let (start_date, end_date): (Date<FixedOffset>, Date<FixedOffset>) = {
        if matches.is_present("range") {
            let range = matches.values_of("range").unwrap();
            let mut range: Vec<DateTime<FixedOffset>> =
                try!(range.map(|a| parsers::parse_datetime(a, ctime))
                    .collect()); // <Result<Vec<DateTime<FixedOffset>>, parsers::ParseError>>
            range.sort();
            (range[0].date(), range[1].date())
        } else {
            let ofst = ctime.offset().to_owned();
            (Date::from_utc(naive::date::MIN, ofst),
             Date::from_utc(naive::date::MAX, ofst))
        }
    };

    if matches.is_present("in") || matches.is_present("out") {
        let dir = if matches.is_present("in") {
            Direction::In
        } else {
            Direction::Out
        };

        let time = match matches.value_of("time") {
            Some(a) => {
                try!(parsers::parse_datetime(&a, now())
                    .or(parsers::parse_offset(&a, now())))
            }
            None => now(),
        };

        let memo = matches.value_of("memo").unwrap_or("").to_owned();
        timeclock::mark_time(dir, time, memo, &mut csv_file);

        println!("Clocked {:#} at {}", dir, time.format("%F %I:%M %P"));

    } else if matches.is_present("summary") {
        try!(print_short_summary(&csv_file, start_date, end_date, rounding));

    } else if matches.is_present("log") {
        try!(print_csv_entries(&csv_file));

    } else if matches.is_present("range") {
        try!(print_short_summary(&csv_file, start_date, end_date, rounding));

    } else {
        let today = now().date();
        let weekday = today.weekday() as i64;
        let days_back = (7 - WEEKSTART + weekday) % 7;
        let start_date = today - Duration::days(days_back);
        try!(print_short_summary(&csv_file, start_date, today, rounding));
    }

    Ok(())
}

fn main() {
    match main0() {
        Ok(_) => {}
        Err(err) => {
            let _ = writeln!(&mut std::io::stderr(), "Error: {}", err);
        }
    };
}
<commit_msg>Replace calls to now() with ctime<commit_after>// Worklog is for recording your hours.
//
extern crate chrono;
extern crate csv;
extern crate clap;
extern crate rustc_serialize;

mod error;
mod timeclock;
mod util;
mod parsers;

use chrono::*;
use clap::{Arg, ArgGroup, App};
use error::WorklogError;
use std::env;
use std::fs::OpenOptions;
use std::io::prelude::*;
use std::path::PathBuf;
use timeclock::Direction;
use timeclock::now;

#[cfg(target_family = "unix")]
static CSV_FILE_NAME: &'static str = ".worklog.csv";
#[cfg(target_family = "windows")]
static CSV_FILE_NAME: &'static str = "worklog.csv";

// default rounding mode
static ROUNDING_DEFAULT: &'static str = "+15m";

// TODO This belongs in a config file somewhere
const WEEKSTART: i64 = Weekday::Sat as i64;

fn print_csv_entries<R: Read>(file: R) -> Result<(), WorklogError> {
    let csv_entries = try!(timeclock::read_timesheet(file));
    for rec in csv_entries {
        println!("{}", rec);
    }
    Ok(())
}

#[allow(dead_code)]
fn print_full_summary<R: Read>(file: R,
                               rounding: util::Rounding)
                               -> Result<(), WorklogError> {
    let csv_entries = try!(timeclock::read_timesheet(file));
    let records = timeclock::collect_date_records(csv_entries);

    let mut total_hours: f64 = 0.0;
    for rec in records {
        let hours = util::round(rec.seconds(), rounding) / 3600.0;
        total_hours += hours;
        println!("{} {:.2} {}", rec.date().format("%F"), hours, rec.memo());
    }

    println!("Total Hours: {:.2}", total_hours);
    Ok(())
}

fn print_short_summary<R: Read>(file: R,
                                start_date: Date<FixedOffset>,
                                end_date: Date<FixedOffset>,
                                rounding: util::Rounding)
                                -> Result<(), WorklogError> {
    let csv_entries = try!(timeclock::read_timesheet(file));
    let records = timeclock::collect_date_records(csv_entries);

    let mut total_hours: f64 = 0.0;
    for rec in records {
        if start_date <= rec.date() && rec.date() <= end_date {
            let hours = util::round(rec.seconds(), rounding) / 3600.0;
            total_hours += hours;
            println!("{} {:.2} {}", rec.date().format("%F"), hours, rec.memo());
        }
    }

    println!("Total Hours: {:.2}", total_hours);
    Ok(())
}

/// Get the path for the csv data file
fn get_csv_path() -> Result<PathBuf, WorklogError> {
    let mut data_path = match env::home_dir() {
        Some(path) => path,
        None => {
            return Err(env::VarError::NotPresent).map_err(WorklogError::Env)
        }
    };

    data_path.push(CSV_FILE_NAME);
    Ok(data_path)
}

fn main0() -> Result<(), WorklogError> {
    // Using std env macro rather than depending on clap's. No difference
    // as far as I can tell.
    // The current options aren't final and will definately change again soon
    let matches = App::new(env!("CARGO_PKG_NAME"))
        .version(env!("CARGO_PKG_VERSION"))
        .author(env!("CARGO_PKG_AUTHORS"))
        .about(env!("CARGO_PKG_DESCRIPTION"))
        .arg(Arg::from_usage("[in] -i, --in 'Record an In entry'"))
        .arg(Arg::from_usage("[out] -o, --out 'Record an Out entry'"))
        .arg(Arg::from_usage("[time] -t, --time <TIME> 'time'")
            .allow_hyphen_values(true)
            .requires("inout"))
        .arg(Arg::from_usage("[memo] -m, --memo <MEMO> 'Memo for the entry'")
            .requires("inout"))
        .arg(Arg::from_usage("[summary] -s, --summary 'Print a summary'")
            .conflicts_with("inout"))
        .arg(Arg::from_usage("[log] -l, --log 'Print the full log'")
            .conflicts_with("summary")
            .conflicts_with("inout"))
        .arg(Arg::from_usage("[round] -r, --round-up 'Round totals up to the next quarter hour'")
            .conflicts_with("log")
            .conflicts_with("inout"))
        .arg(Arg::from_usage("[round_ex] -R, --round <ROUNDING> 'Round totals up, down, half'")
            .allow_hyphen_values(true)
            .conflicts_with("log")
            .conflicts_with("inout"))
        .arg(Arg::from_usage("[range] --range <TIME> <TIME> 'range'")
            .conflicts_with("log")
            .conflicts_with("inout"))
        .group(ArgGroup::with_name("inout").args(&["in", "out"]))
        .get_matches();

    let csv_path = try!(get_csv_path());

    let mut csv_file = try!(OpenOptions::new()
        .read(true)
        .write(true)
        .create(true)
        .open(csv_path));

    let rounding = {
        if matches.occurrences_of("round") > 0 {
            try!(parsers::parse_rounding(ROUNDING_DEFAULT))
        } else if matches.occurrences_of("round_ex") > 0 {
            try!(parsers::parse_rounding(matches.value_of("round_ex").unwrap()))
        } else {
            util::Rounding::None
        }
    };

    let ctime = now();

    let (start_date, end_date): (Date<FixedOffset>, Date<FixedOffset>) = {
        if matches.is_present("range") {
            let range = matches.values_of("range").unwrap();
            let mut range: Vec<DateTime<FixedOffset>> =
                try!(range.map(|a| parsers::parse_datetime(a, ctime))
                    .collect()); // <Result<Vec<DateTime<FixedOffset>>, parsers::ParseError>>
            range.sort();
            (range[0].date(), range[1].date())
        } else {
            let ofst = ctime.offset().to_owned();
            (Date::from_utc(naive::date::MIN, ofst),
             Date::from_utc(naive::date::MAX, ofst))
        }
    };

    if matches.is_present("in") || matches.is_present("out") {
        let dir = if matches.is_present("in") {
            Direction::In
        } else {
            Direction::Out
        };

        let time = match matches.value_of("time") {
            Some(a) => {
                try!(parsers::parse_datetime(&a, ctime)
                    .or(parsers::parse_offset(&a, ctime)))
            }
            None => ctime,
        };

        let memo = matches.value_of("memo").unwrap_or("").to_owned();
        timeclock::mark_time(dir, time, memo, &mut csv_file);

        println!("Clocked {:#} at {}", dir, time.format("%F %I:%M %P"));

    } else if matches.is_present("summary") {
        try!(print_short_summary(&csv_file, start_date, end_date, rounding));

    } else if matches.is_present("log") {
        try!(print_csv_entries(&csv_file));

    } else if matches.is_present("range") {
        try!(print_short_summary(&csv_file, start_date, end_date, rounding));

    } else {
        let today = ctime.date();
        let weekday = today.weekday() as i64;
        let days_back = (7 - WEEKSTART + weekday) % 7;
        let start_date = today - Duration::days(days_back);
        try!(print_short_summary(&csv_file, start_date, today, rounding));
    }

    Ok(())
}

fn main() {
    match main0() {
        Ok(_) => {}
        Err(err) => {
            let _ = writeln!(&mut std::io::stderr(), "Error: {}", err);
        }
    };
}
<|endoftext|>