<commit_before>#[macro_use(crate_version)]
extern crate clap;
extern crate semver;

use std::env;
use std::io::prelude::*;
use clap::{App, AppSettings, SubCommand, Arg, ArgGroup, Values};
use std::process::{exit, Command, Stdio};
use semver::{Version, VersionReq, Identifier, ReqParseError};

/// Information on the rustc compiler version in this environment
struct RustCInfo {
    channel: String,
    version: Version,
}

impl RustCInfo {
    /// Obtains the rust compiler info
    fn get_info() -> RustCInfo {
        // Get RustC version from command output
        let output = Command::new("rustc")
            .arg("-V")
            .stdin(Stdio::null())
            .stderr(Stdio::inherit())
            .output()
            .expect("Failed to get rustc version");
        let output_str = String::from_utf8_lossy(&output.stdout);

        // Parse the string and get the version portion
        let verstr = output_str.split_whitespace()
            .nth(1)
            .expect("Failed to get rustc version string");
        let version = Version::parse(verstr).expect("Failed to parse rustc version");

        // Get channel from pre-release portion of version
        let channel = match version.pre.iter().next() {
            Some(ident) => {
                match ident {
                    &Identifier::AlphaNumeric(ref s) => s.clone(),
                    &Identifier::Numeric(_) => "unknown".to_string(),
                }
            }
            None => "stable".to_string(),
        };
        // Strip pre-release from version
        let version = Version {
            major: version.major,
            minor: version.minor,
            patch: version.patch,
            pre: vec![],
            build: vec![],
        };

        RustCInfo {
            channel: channel,
            version: version,
        }
    }

    /// Does a string match the rust compiler's channel
    fn matches_channel(&self, channel: &str) -> bool {
        self.channel == channel.to_lowercase()
    }

    /// Does the rust compiler version match a version requirement string
    fn matches_version(&self, version: &str) -> Result<bool, ReqParseError> {
        let versreq = try!(VersionReq::parse(version));
        Ok(versreq.matches(&self.version))
    }

    /// Do any of the channel values match?
    fn matches_any_channels<'a, 'b>(&'a self,
                                    channels: Option<Values<'b>>,
                                    when_none: bool)
                                    -> bool {
        channels.map_or(when_none,
                        |mut channels| channels.any(|ch| self.matches_channel(ch)))
    }

    /// Do any of the version values match?
    fn matches_any_versions<'a, 'b>(&'a self,
                                    versions: Option<Values<'b>>,
                                    when_none: bool)
                                    -> Result<bool, ReqParseError> {
        match versions {
            Some(vers) => {
                for v in vers {
                    if try!(self.matches_version(v)) {
                        return Ok(true);
                    }
                }
                Ok(false)
            }
            None => Ok(when_none)
        }
    }
}

/// A parsed environment variable requirement such as 'RUST_SRC_PATH=~/rustsrc'
struct EnvVarReq<'a> {
    name: &'a str,
    value: &'a str,
}

impl<'a> EnvVarReq<'a> {
    /// Parse and validate the environment variable requirement string
    fn parse(var: &'a str) -> Result<EnvVarReq<'a>, String> {
        let mut split = var.splitn(2, '=');

        let name = try!(split.next().ok_or("Environment variable requirement has no name"));
        if name.is_empty() {
            return Err("Invalid environment variable name".to_string());
        }
        let value = try!(split.next()
            .ok_or(format!("Invalid environment variable requirement, expecting: {}=VALUE",
                           name)));

        Ok(EnvVarReq {
            name: name,
            value: value,
        })
    }

    /// Does the requirement match an environment variable value?
    fn matches(&self) -> bool {
        // Don't try to convert OsString to unicode, just conver unicode to OsString.
        env::var_os(self.name).map_or(false, |v| v == *self.value)
    }
}

/// Do any of the environment variables exist?
fn any_env_vars_exist<'a>(vars: Option<Values<'a>>, when_none: bool) -> bool {
    vars.map_or(when_none, |mut vars| vars.any(|v| env::var_os(v).is_some()))
}

/// Do any of the environment variable values match?
fn matches_any_env_vars<'a>(vars: Option<Values<'a>>, when_none: bool) -> Result<bool, String> {
    match vars {
        Some(v) => {
            for var in v {
                let req = try!(EnvVarReq::parse(var));
                if req.matches() {
                    return Ok(true);
                }
            }
            Ok(false)
        }
        None => Ok(when_none),
    }
}

fn main() {
    // CLI
    let matches = App::new("cargo when")
                    .bin_name("cargo")
                    .about(concat!("Runs other cargo commands conditionally upon rust compiler ",
                                    "version and environment."))
                    .version(crate_version!())
                    .setting(AppSettings::SubcommandRequiredElseHelp)
                    //.setting(AppSettings::GlobalVersion)
                    .subcommand(SubCommand::with_name("when")
                        .usage("cargo when [OPTIONS] <CARGO SUBCOMMAND> [SUBCOMMAND OPTIONS]")
                        .about(concat!("Runs subsequent cargo command only when the specified ",
                                        "options match the current rust compiler version and ",
                                        "environment."))
                        .after_help(concat!("To specify a set of multiple possible matches for an ",
                                            "option, separate the values by a comma and no ",
                                            "spaces. At least one match option is required. If ",
                                            "multiple match options are present, each option ",
                                            "specifies an additional match requirement for any of ",
                                            "the set of possible values for that option."))
                        .setting(AppSettings::ArgRequiredElseHelp)
                        .setting(AppSettings::AllowExternalSubcommands)
                        .group(ArgGroup::with_name("matches")
                            .required(true)
                            .multiple(true)
                        )
                        .arg(Arg::with_name("CHANNEL")
                            .short("c")
                            .long("channel")
                            .group("matches")
                            .help("Matches rustc release channel(s)")
                            .takes_value(true)
                            .possible_values(&["stable", "beta", "nightly"])
                            .min_values(1)
                            .require_delimiter(true)
                        )
                        .arg(Arg::with_name("VERSION")
                            .short("v")
                            .long("version")
                            .group("matches")
                            .help(concat!("Matches rustc version(s) using same rules and version ",
                                            "syntax as Cargo"))
                            .takes_value(true)
                            .min_values(1)
                            .require_delimiter(true)
                        )
                        .arg(Arg::with_name("ENV-VARIABLE")
                            .short("x")
                            .long("exists")
                            .group("matches")
                            .help("Matches when environment variable(s) exist")
                            .takes_value(true)
                            .min_values(1)
                            .require_delimiter(true)
                        )
                        .arg(Arg::with_name("ENV-VARIABLE=VALUE")
                            .short("e")
                            .long("equals")
                            .group("matches")
                            .help(concat!("Matches when environment variable(s) equals specified ",
                                            "value(s), e.g. RUST_SRC_PATH=~/rustsrc"))
                            .takes_value(true)
                            .min_values(1)
                            .require_delimiter(true)
                        )
                    )
                    .subcommand(SubCommand::with_name("unless")
                        .usage("cargo unless [OPTIONS] <CARGO SUBCOMMAND> [SUBCOMMAND OPTIONS]")
                        .about(concat!("Runs subsequent cargo command except when the specified ",
                                        "options match the current rust compiler version and ",
                                        "environment. This is the negation of 'cargo when'."))
                        .after_help(concat!("To specify a set of multiple possible matches for an ",
                                            "option, separate the values by a comma and no ",
                                            "spaces. At least one match option is required. If ",
                                            "multiple match options are present, each option ",
                                            "specifies an additional match requirement for any of ",
                                            "the set of possible values for that option."))
                        .setting(AppSettings::ArgRequiredElseHelp)
                        .setting(AppSettings::AllowExternalSubcommands)
                        .group(ArgGroup::with_name("matches")
                            .required(true)
                            .multiple(true)
                        )
                        .arg(Arg::with_name("CHANNEL")
                            .short("c")
                            .long("channel")
                            .group("matches")
                            .help("Matches rustc release channel(s)")
                            .takes_value(true)
                            .possible_values(&["stable", "beta", "nightly"])
                            .min_values(1)
                            .require_delimiter(true)
                        )
                        .arg(Arg::with_name("VERSION")
                            .short("v")
                            .long("version")
                            .group("matches")
                            .help(concat!("Matches rustc version(s) using same rules and version ",
                                            "syntax as Cargo"))
                            .takes_value(true)
                            .min_values(1)
                            .require_delimiter(true)
                        )
                        .arg(Arg::with_name("ENV-VARIABLE")
                            .short("x")
                            .long("exists")
                            .group("matches")
                            .help("Matches when environment variable(s) exist")
                            .takes_value(true)
                            .min_values(1)
                            .require_delimiter(true)
                        )
                        .arg(Arg::with_name("ENV-VARIABLE=VALUE")
                            .short("e")
                            .long("equals")
                            .group("matches")
                            .help(concat!("Matches when environment variable(s) equals specified ",
                                            "value(s), e.g. RUST_SRC_PATH=~/rustsrc"))
                            .takes_value(true)
                            .min_values(1)
                            .require_delimiter(true)
                        )
                    ).get_matches();

    // Query rustc version
    let rustc_info = RustCInfo::get_info();

    // Check conditions, gets command if matches, None if not
    let command = match matches.subcommand() {
        ("when", Some(sub)) => {
            // Do all the provided match options match the current compiler and environment?
            let env_matches = matches_any_env_vars(sub.values_of("ENV-VARIABLE=VALUE"), true);
            if env_matches.is_err() {
                writeln!(std::io::stderr(), "{}", env_matches.unwrap_err()).ok();
                writeln!(std::io::stderr(), "{}", sub.usage()).ok();
                exit(1);
            }

            let vers_matches = rustc_info.matches_any_versions(sub.values_of("VERSION"), true);
            if vers_matches.is_err() {
                writeln!(std::io::stderr(), "{}", vers_matches.unwrap_err()).ok();
                writeln!(std::io::stderr(), "{}", sub.usage()).ok();
                exit(1);
            }

            if rustc_info.matches_any_channels(sub.values_of("CHANNEL"), true) &&
               vers_matches.unwrap_or(false) &&
               any_env_vars_exist(sub.values_of("ENV-VARIABLE"), true) &&
               env_matches.unwrap_or(false) {

                match sub.subcommand() {
                    (external, Some(extm)) => {
                        let mut cmd = vec![external];
                        if let Some(vals) = extm.values_of("") {
                            cmd.extend(vals);
                        }
                        Some(cmd)
                    }
                    _ => {
                        // No cargo subcommand was provided, print usage help message and exit
                        writeln!(std::io::stderr(), "{}", sub.usage()).ok();
                        exit(1);
                    }
                }
            } else {
                None
            }
        }
        ("unless", Some(sub)) => {
            // Do none of the provided match options match the current compiler and environment?
            let env_matches = matches_any_env_vars(sub.values_of("ENV-VARIABLE=VALUE"), false);
            if env_matches.is_err() {
                writeln!(std::io::stderr(), "{}", env_matches.unwrap_err()).ok();
                writeln!(std::io::stderr(), "{}", sub.usage()).ok();
                exit(1);
            }

            let vers_matches = rustc_info.matches_any_versions(sub.values_of("VERSION"), false);
            if vers_matches.is_err() {
                writeln!(std::io::stderr(), "{}", vers_matches.unwrap_err()).ok();
                writeln!(std::io::stderr(), "{}", sub.usage()).ok();
                exit(1);
            }

            if !rustc_info.matches_any_channels(sub.values_of("CHANNEL"), false) &&
               !vers_matches.unwrap_or(true) &&
               !any_env_vars_exist(sub.values_of("ENV-VARIABLE"), false) &&
               !env_matches.unwrap_or(true) {

                match sub.subcommand() {
                    (external, Some(extm)) => {
                        let mut cmd = vec![external];
                        if let Some(vals) = extm.values_of("") {
                            cmd.extend(vals);
                        }
                        Some(cmd)
                    }
                    _ => {
                        // No cargo subcommand was provided, print usage help message and exit
                        writeln!(std::io::stderr(), "{}", sub.usage()).ok();
                        exit(1);
                    }
                }
            } else {
                None
            }
        }
        _ => None,
    };

    // If we're a match, the chained cargo command will be provided, otherwise, we do nothing
    if let Some(args) = command {
        let status = Command::new("cargo")
            .args(&args)
            .stdin(Stdio::inherit())
            .stdout(Stdio::inherit())
            .stderr(Stdio::inherit())
            .status();

        match status {
            Ok(s) => {
                if !s.success() {
                    exit(s.code().unwrap_or(127));
                }
            }
            Err(_) => {
                exit(127);
            }
        }
    }
}
<commit_msg>style: rustfmt<commit_after>#[macro_use(crate_version)]
extern crate clap;
extern crate semver;

use std::env;
use std::io::prelude::*;
use clap::{App, AppSettings, SubCommand, Arg, ArgGroup, Values};
use std::process::{exit, Command, Stdio};
use semver::{Version, VersionReq, Identifier, ReqParseError};

/// Information on the rustc compiler version in this environment
struct RustCInfo {
    channel: String,
    version: Version,
}

impl RustCInfo {
    /// Obtains the rust compiler info
    fn get_info() -> RustCInfo {
        // Get RustC version from command output
        let output = Command::new("rustc")
            .arg("-V")
            .stdin(Stdio::null())
            .stderr(Stdio::inherit())
            .output()
            .expect("Failed to get rustc version");
        let output_str = String::from_utf8_lossy(&output.stdout);

        // Parse the string and get the version portion
        let verstr = output_str.split_whitespace()
            .nth(1)
            .expect("Failed to get rustc version string");
        let version = Version::parse(verstr).expect("Failed to parse rustc version");

        // Get channel from pre-release portion of version
        let channel = match version.pre.iter().next() {
            Some(ident) => {
                match ident {
                    &Identifier::AlphaNumeric(ref s) => s.clone(),
                    &Identifier::Numeric(_) => "unknown".to_string(),
                }
            }
            None => "stable".to_string(),
        };
        // Strip pre-release from version
        let version = Version {
            major: version.major,
            minor: version.minor,
            patch: version.patch,
            pre: vec![],
            build: vec![],
        };

        RustCInfo {
            channel: channel,
            version: version,
        }
    }

    /// Does a string match the rust compiler's channel
    fn matches_channel(&self, channel: &str) -> bool {
        self.channel == channel.to_lowercase()
    }

    /// Does the rust compiler version match a version requirement string
    fn matches_version(&self, version: &str) -> Result<bool, ReqParseError> {
        let versreq = try!(VersionReq::parse(version));
        Ok(versreq.matches(&self.version))
    }

    /// Do any of the channel values match?
    fn matches_any_channels<'a, 'b>(&'a self,
                                    channels: Option<Values<'b>>,
                                    when_none: bool)
                                    -> bool {
        channels.map_or(when_none,
                        |mut channels| channels.any(|ch| self.matches_channel(ch)))
    }

    /// Do any of the version values match?
    fn matches_any_versions<'a, 'b>(&'a self,
                                    versions: Option<Values<'b>>,
                                    when_none: bool)
                                    -> Result<bool, ReqParseError> {
        match versions {
            Some(vers) => {
                for v in vers {
                    if try!(self.matches_version(v)) {
                        return Ok(true);
                    }
                }
                Ok(false)
            }
            None => Ok(when_none),
        }
    }
}

/// A parsed environment variable requirement such as 'RUST_SRC_PATH=~/rustsrc'
struct EnvVarReq<'a> {
    name: &'a str,
    value: &'a str,
}

impl<'a> EnvVarReq<'a> {
    /// Parse and validate the environment variable requirement string
    fn parse(var: &'a str) -> Result<EnvVarReq<'a>, String> {
        let mut split = var.splitn(2, '=');

        let name = try!(split.next().ok_or("Environment variable requirement has no name"));
        if name.is_empty() {
            return Err("Invalid environment variable name".to_string());
        }
        let value = try!(split.next()
            .ok_or(format!("Invalid environment variable requirement, expecting: {}=VALUE",
                           name)));

        Ok(EnvVarReq {
            name: name,
            value: value,
        })
    }

    /// Does the requirement match an environment variable value?
    fn matches(&self) -> bool {
        // Don't try to convert OsString to unicode, just conver unicode to OsString.
        env::var_os(self.name).map_or(false, |v| v == *self.value)
    }
}

/// Do any of the environment variables exist?
fn any_env_vars_exist<'a>(vars: Option<Values<'a>>, when_none: bool) -> bool {
    vars.map_or(when_none, |mut vars| vars.any(|v| env::var_os(v).is_some()))
}

/// Do any of the environment variable values match?
fn matches_any_env_vars<'a>(vars: Option<Values<'a>>, when_none: bool) -> Result<bool, String> {
    match vars {
        Some(v) => {
            for var in v {
                let req = try!(EnvVarReq::parse(var));
                if req.matches() {
                    return Ok(true);
                }
            }
            Ok(false)
        }
        None => Ok(when_none),
    }
}

fn main() {
    // CLI
    let matches = App::new("cargo when")
                    .bin_name("cargo")
                    .about(concat!("Runs other cargo commands conditionally upon rust compiler ",
                                    "version and environment."))
                    .version(crate_version!())
                    .setting(AppSettings::SubcommandRequiredElseHelp)
                    //.setting(AppSettings::GlobalVersion)
                    .subcommand(SubCommand::with_name("when")
                        .usage("cargo when [OPTIONS] <CARGO SUBCOMMAND> [SUBCOMMAND OPTIONS]")
                        .about(concat!("Runs subsequent cargo command only when the specified ",
                                        "options match the current rust compiler version and ",
                                        "environment."))
                        .after_help(concat!("To specify a set of multiple possible matches for an ",
                                            "option, separate the values by a comma and no ",
                                            "spaces. At least one match option is required. If ",
                                            "multiple match options are present, each option ",
                                            "specifies an additional match requirement for any of ",
                                            "the set of possible values for that option."))
                        .setting(AppSettings::ArgRequiredElseHelp)
                        .setting(AppSettings::AllowExternalSubcommands)
                        .group(ArgGroup::with_name("matches")
                            .required(true)
                            .multiple(true)
                        )
                        .arg(Arg::with_name("CHANNEL")
                            .short("c")
                            .long("channel")
                            .group("matches")
                            .help("Matches rustc release channel(s)")
                            .takes_value(true)
                            .possible_values(&["stable", "beta", "nightly"])
                            .min_values(1)
                            .require_delimiter(true)
                        )
                        .arg(Arg::with_name("VERSION")
                            .short("v")
                            .long("version")
                            .group("matches")
                            .help(concat!("Matches rustc version(s) using same rules and version ",
                                            "syntax as Cargo"))
                            .takes_value(true)
                            .min_values(1)
                            .require_delimiter(true)
                        )
                        .arg(Arg::with_name("ENV-VARIABLE")
                            .short("x")
                            .long("exists")
                            .group("matches")
                            .help("Matches when environment variable(s) exist")
                            .takes_value(true)
                            .min_values(1)
                            .require_delimiter(true)
                        )
                        .arg(Arg::with_name("ENV-VARIABLE=VALUE")
                            .short("e")
                            .long("equals")
                            .group("matches")
                            .help(concat!("Matches when environment variable(s) equals specified ",
                                            "value(s), e.g. RUST_SRC_PATH=~/rustsrc"))
                            .takes_value(true)
                            .min_values(1)
                            .require_delimiter(true)
                        )
                    )
                    .subcommand(SubCommand::with_name("unless")
                        .usage("cargo unless [OPTIONS] <CARGO SUBCOMMAND> [SUBCOMMAND OPTIONS]")
                        .about(concat!("Runs subsequent cargo command except when the specified ",
                                        "options match the current rust compiler version and ",
                                        "environment. This is the negation of 'cargo when'."))
                        .after_help(concat!("To specify a set of multiple possible matches for an ",
                                            "option, separate the values by a comma and no ",
                                            "spaces. At least one match option is required. If ",
                                            "multiple match options are present, each option ",
                                            "specifies an additional match requirement for any of ",
                                            "the set of possible values for that option."))
                        .setting(AppSettings::ArgRequiredElseHelp)
                        .setting(AppSettings::AllowExternalSubcommands)
                        .group(ArgGroup::with_name("matches")
                            .required(true)
                            .multiple(true)
                        )
                        .arg(Arg::with_name("CHANNEL")
                            .short("c")
                            .long("channel")
                            .group("matches")
                            .help("Matches rustc release channel(s)")
                            .takes_value(true)
                            .possible_values(&["stable", "beta", "nightly"])
                            .min_values(1)
                            .require_delimiter(true)
                        )
                        .arg(Arg::with_name("VERSION")
                            .short("v")
                            .long("version")
                            .group("matches")
                            .help(concat!("Matches rustc version(s) using same rules and version ",
                                            "syntax as Cargo"))
                            .takes_value(true)
                            .min_values(1)
                            .require_delimiter(true)
                        )
                        .arg(Arg::with_name("ENV-VARIABLE")
                            .short("x")
                            .long("exists")
                            .group("matches")
                            .help("Matches when environment variable(s) exist")
                            .takes_value(true)
                            .min_values(1)
                            .require_delimiter(true)
                        )
                        .arg(Arg::with_name("ENV-VARIABLE=VALUE")
                            .short("e")
                            .long("equals")
                            .group("matches")
                            .help(concat!("Matches when environment variable(s) equals specified ",
                                            "value(s), e.g. RUST_SRC_PATH=~/rustsrc"))
                            .takes_value(true)
                            .min_values(1)
                            .require_delimiter(true)
                        )
                    ).get_matches();

    // Query rustc version
    let rustc_info = RustCInfo::get_info();

    // Check conditions, gets command if matches, None if not
    let command = match matches.subcommand() {
        ("when", Some(sub)) => {
            // Do all the provided match options match the current compiler and environment?
            let env_matches = matches_any_env_vars(sub.values_of("ENV-VARIABLE=VALUE"), true);
            if env_matches.is_err() {
                writeln!(std::io::stderr(), "{}", env_matches.unwrap_err()).ok();
                writeln!(std::io::stderr(), "{}", sub.usage()).ok();
                exit(1);
            }

            let vers_matches = rustc_info.matches_any_versions(sub.values_of("VERSION"), true);
            if vers_matches.is_err() {
                writeln!(std::io::stderr(), "{}", vers_matches.unwrap_err()).ok();
                writeln!(std::io::stderr(), "{}", sub.usage()).ok();
                exit(1);
            }

            if rustc_info.matches_any_channels(sub.values_of("CHANNEL"), true) &&
               vers_matches.unwrap_or(false) &&
               any_env_vars_exist(sub.values_of("ENV-VARIABLE"), true) &&
               env_matches.unwrap_or(false) {

                match sub.subcommand() {
                    (external, Some(extm)) => {
                        let mut cmd = vec![external];
                        if let Some(vals) = extm.values_of("") {
                            cmd.extend(vals);
                        }
                        Some(cmd)
                    }
                    _ => {
                        // No cargo subcommand was provided, print usage help message and exit
                        writeln!(std::io::stderr(), "{}", sub.usage()).ok();
                        exit(1);
                    }
                }
            } else {
                None
            }
        }
        ("unless", Some(sub)) => {
            // Do none of the provided match options match the current compiler and environment?
            let env_matches = matches_any_env_vars(sub.values_of("ENV-VARIABLE=VALUE"), false);
            if env_matches.is_err() {
                writeln!(std::io::stderr(), "{}", env_matches.unwrap_err()).ok();
                writeln!(std::io::stderr(), "{}", sub.usage()).ok();
                exit(1);
            }

            let vers_matches = rustc_info.matches_any_versions(sub.values_of("VERSION"), false);
            if vers_matches.is_err() {
                writeln!(std::io::stderr(), "{}", vers_matches.unwrap_err()).ok();
                writeln!(std::io::stderr(), "{}", sub.usage()).ok();
                exit(1);
            }

            if !rustc_info.matches_any_channels(sub.values_of("CHANNEL"), false) &&
               !vers_matches.unwrap_or(true) &&
               !any_env_vars_exist(sub.values_of("ENV-VARIABLE"), false) &&
               !env_matches.unwrap_or(true) {

                match sub.subcommand() {
                    (external, Some(extm)) => {
                        let mut cmd = vec![external];
                        if let Some(vals) = extm.values_of("") {
                            cmd.extend(vals);
                        }
                        Some(cmd)
                    }
                    _ => {
                        // No cargo subcommand was provided, print usage help message and exit
                        writeln!(std::io::stderr(), "{}", sub.usage()).ok();
                        exit(1);
                    }
                }
            } else {
                None
            }
        }
        _ => None,
    };

    // If we're a match, the chained cargo command will be provided, otherwise, we do nothing
    if let Some(args) = command {
        let status = Command::new("cargo")
            .args(&args)
            .stdin(Stdio::inherit())
            .stdout(Stdio::inherit())
            .stderr(Stdio::inherit())
            .status();

        match status {
            Ok(s) => {
                if !s.success() {
                    exit(s.code().unwrap_or(127));
                }
            }
            Err(_) => {
                exit(127);
            }
        }
    }
}
<|endoftext|>


<commit_before>// #![feature(plugin)]
// #![plugin(clippy)]

#[macro_use]
extern crate hyper;
extern crate chrono;
extern crate url;
extern crate crypto;
extern crate rustc_serialize;
extern crate xml;
#[macro_use]
extern crate mime;
extern crate time;

#[macro_use]
extern crate log;
extern crate env_logger;

extern crate uuid;

use azure::core::lease::{LeaseState, LeaseStatus, LeaseAction};
use azure::storage::client::Client;
use azure::storage::blob::{Blob, BlobType, LIST_BLOB_OPTIONS_DEFAULT, PUT_OPTIONS_DEFAULT,
                           PUT_BLOCK_OPTIONS_DEFAULT, PUT_PAGE_OPTIONS_DEFAULT,
                           LEASE_BLOB_OPTIONS_DEFAULT};
use azure::storage::container::{Container, PublicAccess, LIST_CONTAINER_OPTIONS_DEFAULT};
use azure::core::ba512_range::BA512Range;

use std::fs;
use time::Duration;

// use azure::storage::container::PublicAccess;

#[macro_use]
pub mod azure;

// use chrono::datetime::DateTime;
use chrono::UTC;

use mime::Mime;

use azure::storage::table::TableClient;

fn get_from_env(varname: &str) -> String {
    match std::env::var(varname) {
        Ok(val) => val,
        Err(_) => {
            panic!("Please set {} env variable first!", varname);
        }
    }
}

fn create_storage_client() -> Client {
    let azure_storage_account = get_from_env("AZURE_STORAGE_ACCOUNT");
    let azure_storage_key = get_from_env("AZURE_STORAGE_KEY");
    Client::new(&azure_storage_account, &azure_storage_key, true)
}

fn main() {
    env_logger::init().unwrap();
    let client = TableClient::new(create_storage_client());
    for x in client.list().unwrap() {
        println!("{}", x);
    }

    // Table::insert(&client, "rtest1", "a61", "b15", "c:mot").unwrap();
    // let f= Table::query(&client, "rtest1", "a61", "b15", "c");
    // println!("{}",f.unwrap());
    // Blob::del(&client, "slstore1", "d").unwrap();
}

#[allow(dead_code)]
#[allow(unused_variables)]
fn main_old() {
    env_logger::init().unwrap();

    let azure_storage_account = match std::env::var("AZURE_STORAGE_ACCOUNT") {
        Ok(val) => val,
        Err(_) => {
            panic!("Please set AZURE_STORAGE_ACCOUNT env variable first!");
        }
    };

    let azure_storage_key = match std::env::var("AZURE_STORAGE_KEY") {
        Ok(val) => val,
        Err(_) => {
            panic!("Please set AZURE_STORAGE_KEY env variable first!");
        }
    };

    let client = Client::new(&azure_storage_account, &azure_storage_key, true);

    let policy_name = match std::env::var("AZURE_POLICY_NAME") {
        Ok(val) => val,
        Err(_) => {
            panic!("Please set AZURE_POLICY_NAME env variable first!");
        }
    };

    let policy_key = match std::env::var("AZURE_POLICY_KEY") {
        Ok(val) => val,
        Err(_) => {
            panic!("Please set AZURE_POLICY_KEY env variable first!");
        }
    };

    let sb_namespace = match std::env::var("AZURE_SERVICE_BUS_NAMESPACE") {
        Ok(val) => val,
        Err(_) => {
            panic!("Please set AZURE_SERVICE_BUS_NAMESPACE env variable first!");
        }
    };

    let ev_name = match std::env::var("AZURE_EVENT_HUB_NAME") {
        Ok(val) => val,
        Err(_) => {
            panic!("Please set AZURE_EVENT_HUB_NAME env variable first!");
        }
    };

    let mut eh_client = azure::service_bus::event_hub::Client::new(&sb_namespace,
                                                                   &ev_name,
                                                                   &policy_name,
                                                                   &policy_key);
    // "todeleh",
    // "write_policy",
    // "9GIzBhQhMKg/patjrI2XS6gSGn6ju2+N40CQEYmowJ8=");

    // client.create_container("balocco3", PublicAccess::Blob).unwrap();
    // // println!("{:?}", new);
    //

    info!("Beginning tests");

    for i in 0..20 {
        info!("Sending message {}", i);
        send_event(&mut eh_client);
    }

    // lease_blob(&client);

    // put_block_blob(&client);
    //
    // put_page_blob(&client);

    // {
    //     let vhds = ret.iter_mut().find(|x| x.name == "canotto").unwrap();
    //
    //     let blobs = vhds.list_blobs(&client, true, true, true, true).unwrap();
    //
    //     println!("len == {:?}", blobs.len());
    //
    //     for blob in &blobs {
    //         println!("{}, {} KB ({:?})",
    //                  blob.name,
    //                  (blob.content_length / 1024),
    //                  blob.lease_state)
    //     }
    //
    //     let (blob, mut stream) = vhds.get_blob(&client, "DataCollector01.csv", None, None, None)
    //                                  .unwrap();
    //     println!("blob == {:?}", blob);
    //
    //     let mut buffer = String::new();
    //     stream.read_to_string(&mut buffer).unwrap();
    //
    //     // println!("buffer == {:?}", buffer);
    // }

    // for i in 0..2 {
    //     use std::fs::metadata;
    //     use std::fs::File;
    //
    //     let file_name: &'static str = "C:\\temp\\prova.txt";
    //     let container_name: &'static str = "rust";
    //
    //     {
    //         let containers = Container::list(&client).unwrap();
    //
    //         let cont = containers.iter().find(|x| x.name == container_name);
    //         if let None = cont {
    //             Container::create(&client, container_name, PublicAccess::Blob).unwrap();
    //         }
    //     }
    //
    //     let metadata = metadata(file_name).unwrap();
    //     let mut file = File::open(file_name).unwrap();
    //
    //     let new_blob = Blob {
    //         name: format!("go_rust{}.txt", i),
    //         snapshot_time: None,
    //         last_modified: UTC::now(),
    //         etag: "".to_owned(),
    //         content_length: metadata.len(),
    //         content_type: "application/octet-stream".parse::<Mime>().unwrap(),
    //         content_encoding: None,
    //         content_language: None,
    //         content_md5: None,
    //         cache_control: None,
    //         x_ms_blob_sequence_number: None,
    //         blob_type: BlobType::BlockBlob,
    //         lease_status: LeaseStatus::Unlocked,
    //         lease_state: LeaseState::Available,
    //         lease_duration: None,
    //         copy_id: None,
    //         copy_status: None,
    //         copy_source: None,
    //         copy_progress: None,
    //         copy_completion: None,
    //         copy_status_description: None,
    //     };
    //
    //     new_blob.put(&client,
    //                  container_name,
    //                  None,
    //                  Some((&mut file, metadata.len())))
    //             .unwrap();
    //
    //     println!("{} created", new_blob.name);
    // }

    // bal2.delete(&client).unwrap();
    // println!("{:?} deleted!", bal2);

    // let ret = client.delete_container("balocco2").unwrap();
    // println!("{:?}", ret);
    // inc_a!("main");
}

#[allow(dead_code)]
fn send_event(cli: &mut azure::service_bus::event_hub::Client) {
    debug!("running send_event");
    let file_name = "C:\\temp\\samplein.json";

    let metadata = fs::metadata(file_name).unwrap();
    let mut file_handle = fs::File::open(file_name).unwrap();

    cli.send_event((&mut file_handle, metadata.len()), Duration::hours(1)).unwrap();
}

#[allow(dead_code)]
fn lease_blob(client: &Client) {
    println!("running lease_blob");

    let ret = Container::list(client, &LIST_CONTAINER_OPTIONS_DEFAULT).unwrap();
    let vhds = ret.iter().find(|x| x.name == "rust").unwrap();
    let blobs = Blob::list(&client, &vhds.name, &LIST_BLOB_OPTIONS_DEFAULT).unwrap();
    let blob = blobs.iter().find(|ref x| x.name == "go_rust12.txt").unwrap();

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

    let mut lbo = LEASE_BLOB_OPTIONS_DEFAULT.clone();
    lbo.lease_duration = Some(30);
    let ret = blob.lease(client, LeaseAction::Acquire, &lbo).unwrap();
    println!("ret == {:?}", ret);

}

#[allow(dead_code)]
fn list_blobs(client: &Client) {
    println!("running list_blobs");

    let mut lbo2 = LIST_BLOB_OPTIONS_DEFAULT.clone();
    lbo2.max_results = 15;

    loop {
        let uc = Blob::list(&client, "rust", &lbo2).unwrap();

        println!("uc {:?}\n\n", uc);

        if !uc.is_complete() {
            lbo2.next_marker = Some(uc.next_marker().unwrap().to_owned());
        } else {
            break;
        }
    }
}

#[allow(dead_code)]
fn list_containers(client: &Client) {
    println!("running list_containers");

    let mut lco = LIST_CONTAINER_OPTIONS_DEFAULT.clone();
    lco.max_results = 2;
    loop {
        let ret = Container::list(&client, &lco).unwrap();

        println!("ret {:?}\n\n", ret);

        if !ret.is_complete() {
            lco.next_marker = Some(ret.next_marker().unwrap().to_owned());
        } else {
            break;
        }
    }
}

#[allow(dead_code)]
fn put_block_blob(client: &Client) {
    use std::fs::metadata;
    use std::fs::File;

    println!("\nrunning put_block_blob");

    let blob_name: &'static str = "Win64OpenSSL-1_0_2e.exe";
    let file_name: &'static str = "C:\\temp\\Win64OpenSSL-1_0_2e.exe";
    let container_name: &'static str = "rust";
    let metadata = metadata(file_name).unwrap();
    let mut file = File::open(file_name).unwrap();

    let content_length = metadata.len();

    {
        let containers = Container::list(&client, &LIST_CONTAINER_OPTIONS_DEFAULT).unwrap();

        let cont = containers.iter().find(|x| x.name == container_name);
        if let None = cont {
            Container::create(client, container_name, PublicAccess::Blob).unwrap();
        }
    }

    let new_blob = Blob {
        name: blob_name.to_owned(),
        container_name: container_name.to_owned(),
        snapshot_time: None,
        last_modified: UTC::now(),
        etag: "".to_owned(),
        content_length: content_length,
        content_type: "application/octet-stream".parse::<Mime>().unwrap(),
        content_encoding: None,
        content_language: None,
        content_md5: None,
        cache_control: None,
        x_ms_blob_sequence_number: None,
        blob_type: BlobType::BlockBlob,
        lease_status: LeaseStatus::Unlocked,
        lease_state: LeaseState::Available,
        lease_duration: None,
        copy_id: None,
        copy_status: None,
        copy_source: None,
        copy_progress: None,
        copy_completion: None,
        copy_status_description: None,
    };

    new_blob.put_block(&client,
                       "block_name",
                       &PUT_BLOCK_OPTIONS_DEFAULT,
                       (&mut file, 1024 * 1024))
        .unwrap();

    println!("created {:?}", new_blob);
}

#[allow(dead_code)]
fn put_page_blob(client: &Client) {
    use std::fs::metadata;
    use std::fs::File;

    println!("\nrunning put_page_blob");

    let blob_name: &'static str = "MindDB_Log.ldf";
    let file_name: &'static str = "C:\\temp\\MindDB_Log.ldf";
    let container_name: &'static str = "rust";
    let metadata = metadata(file_name).unwrap();
    let mut file = File::open(file_name).unwrap();

    {
        let containers = Container::list(&client, &LIST_CONTAINER_OPTIONS_DEFAULT).unwrap();

        let cont = containers.iter().find(|x| x.name == container_name);
        if let None = cont {
            Container::create(client, container_name, PublicAccess::Blob).unwrap();
        }
    }

    // align to 512 bytes
    let content_length = metadata.len() % 512 + metadata.len();

    let new_blob = Blob {
        name: blob_name.to_owned(),
        container_name: container_name.to_owned(),
        snapshot_time: None,
        last_modified: UTC::now(),
        etag: "".to_owned(),
        content_length: content_length,
        content_type: "application/octet-stream".parse::<Mime>().unwrap(),
        content_encoding: None,
        content_language: None,
        content_md5: None,
        cache_control: None,
        x_ms_blob_sequence_number: None,
        blob_type: BlobType::PageBlob,
        lease_status: LeaseStatus::Unlocked,
        lease_state: LeaseState::Available,
        lease_duration: None,
        copy_id: None,
        copy_status: None,
        copy_source: None,
        copy_progress: None,
        copy_completion: None,
        copy_status_description: None,
    };

    new_blob.put(&client, &PUT_OPTIONS_DEFAULT, None).unwrap();

    let range = BA512Range::new(0, 1024 * 1024 - 1).unwrap();

    new_blob.put_page(client,
                      &range, // 1MB
                      &PUT_PAGE_OPTIONS_DEFAULT,
                      (&mut file, range.size()))
        .unwrap();

    println!("created {:?}", new_blob);
}
<commit_msg>Remove table test in main.rs .<commit_after>// #![feature(plugin)]
// #![plugin(clippy)]

#[macro_use]
extern crate hyper;
extern crate chrono;
extern crate url;
extern crate crypto;
extern crate rustc_serialize;
extern crate xml;
#[macro_use]
extern crate mime;
extern crate time;

#[macro_use]
extern crate log;
extern crate env_logger;

extern crate uuid;

use azure::core::lease::{LeaseState, LeaseStatus, LeaseAction};
use azure::storage::client::Client;
use azure::storage::blob::{Blob, BlobType, LIST_BLOB_OPTIONS_DEFAULT, PUT_OPTIONS_DEFAULT,
                           PUT_BLOCK_OPTIONS_DEFAULT, PUT_PAGE_OPTIONS_DEFAULT,
                           LEASE_BLOB_OPTIONS_DEFAULT};
use azure::storage::container::{Container, PublicAccess, LIST_CONTAINER_OPTIONS_DEFAULT};
use azure::core::ba512_range::BA512Range;

use std::fs;
use time::Duration;

// use azure::storage::container::PublicAccess;

#[macro_use]
pub mod azure;

// use chrono::datetime::DateTime;
use chrono::UTC;

use mime::Mime;

#[allow(unused_variables)]
fn main() {
    env_logger::init().unwrap();

    let azure_storage_account = match std::env::var("AZURE_STORAGE_ACCOUNT") {
        Ok(val) => val,
        Err(_) => {
            panic!("Please set AZURE_STORAGE_ACCOUNT env variable first!");
        }
    };

    let azure_storage_key = match std::env::var("AZURE_STORAGE_KEY") {
        Ok(val) => val,
        Err(_) => {
            panic!("Please set AZURE_STORAGE_KEY env variable first!");
        }
    };

    let client = Client::new(&azure_storage_account, &azure_storage_key, true);

    let policy_name = match std::env::var("AZURE_POLICY_NAME") {
        Ok(val) => val,
        Err(_) => {
            panic!("Please set AZURE_POLICY_NAME env variable first!");
        }
    };

    let policy_key = match std::env::var("AZURE_POLICY_KEY") {
        Ok(val) => val,
        Err(_) => {
            panic!("Please set AZURE_POLICY_KEY env variable first!");
        }
    };

    let sb_namespace = match std::env::var("AZURE_SERVICE_BUS_NAMESPACE") {
        Ok(val) => val,
        Err(_) => {
            panic!("Please set AZURE_SERVICE_BUS_NAMESPACE env variable first!");
        }
    };

    let ev_name = match std::env::var("AZURE_EVENT_HUB_NAME") {
        Ok(val) => val,
        Err(_) => {
            panic!("Please set AZURE_EVENT_HUB_NAME env variable first!");
        }
    };

    let mut eh_client = azure::service_bus::event_hub::Client::new(&sb_namespace,
                                                                   &ev_name,
                                                                   &policy_name,
                                                                   &policy_key);
    // "todeleh",
    // "write_policy",
    // "9GIzBhQhMKg/patjrI2XS6gSGn6ju2+N40CQEYmowJ8=");

    // client.create_container("balocco3", PublicAccess::Blob).unwrap();
    // // println!("{:?}", new);
    //

    info!("Beginning tests");

    for i in 0..20 {
        info!("Sending message {}", i);
        send_event(&mut eh_client);
    }

    // lease_blob(&client);

    // put_block_blob(&client);
    //
    // put_page_blob(&client);

    // {
    //     let vhds = ret.iter_mut().find(|x| x.name == "canotto").unwrap();
    //
    //     let blobs = vhds.list_blobs(&client, true, true, true, true).unwrap();
    //
    //     println!("len == {:?}", blobs.len());
    //
    //     for blob in &blobs {
    //         println!("{}, {} KB ({:?})",
    //                  blob.name,
    //                  (blob.content_length / 1024),
    //                  blob.lease_state)
    //     }
    //
    //     let (blob, mut stream) = vhds.get_blob(&client, "DataCollector01.csv", None, None, None)
    //                                  .unwrap();
    //     println!("blob == {:?}", blob);
    //
    //     let mut buffer = String::new();
    //     stream.read_to_string(&mut buffer).unwrap();
    //
    //     // println!("buffer == {:?}", buffer);
    // }

    // for i in 0..2 {
    //     use std::fs::metadata;
    //     use std::fs::File;
    //
    //     let file_name: &'static str = "C:\\temp\\prova.txt";
    //     let container_name: &'static str = "rust";
    //
    //     {
    //         let containers = Container::list(&client).unwrap();
    //
    //         let cont = containers.iter().find(|x| x.name == container_name);
    //         if let None = cont {
    //             Container::create(&client, container_name, PublicAccess::Blob).unwrap();
    //         }
    //     }
    //
    //     let metadata = metadata(file_name).unwrap();
    //     let mut file = File::open(file_name).unwrap();
    //
    //     let new_blob = Blob {
    //         name: format!("go_rust{}.txt", i),
    //         snapshot_time: None,
    //         last_modified: UTC::now(),
    //         etag: "".to_owned(),
    //         content_length: metadata.len(),
    //         content_type: "application/octet-stream".parse::<Mime>().unwrap(),
    //         content_encoding: None,
    //         content_language: None,
    //         content_md5: None,
    //         cache_control: None,
    //         x_ms_blob_sequence_number: None,
    //         blob_type: BlobType::BlockBlob,
    //         lease_status: LeaseStatus::Unlocked,
    //         lease_state: LeaseState::Available,
    //         lease_duration: None,
    //         copy_id: None,
    //         copy_status: None,
    //         copy_source: None,
    //         copy_progress: None,
    //         copy_completion: None,
    //         copy_status_description: None,
    //     };
    //
    //     new_blob.put(&client,
    //                  container_name,
    //                  None,
    //                  Some((&mut file, metadata.len())))
    //             .unwrap();
    //
    //     println!("{} created", new_blob.name);
    // }

    // bal2.delete(&client).unwrap();
    // println!("{:?} deleted!", bal2);

    // let ret = client.delete_container("balocco2").unwrap();
    // println!("{:?}", ret);
    // inc_a!("main");
}

#[allow(dead_code)]
fn send_event(cli: &mut azure::service_bus::event_hub::Client) {
    debug!("running send_event");
    let file_name = "C:\\temp\\samplein.json";

    let metadata = fs::metadata(file_name).unwrap();
    let mut file_handle = fs::File::open(file_name).unwrap();

    cli.send_event((&mut file_handle, metadata.len()), Duration::hours(1))
       .unwrap();
}

#[allow(dead_code)]
fn lease_blob(client: &Client) {
    println!("running lease_blob");

    let ret = Container::list(client, &LIST_CONTAINER_OPTIONS_DEFAULT).unwrap();
    let vhds = ret.iter().find(|x| x.name == "rust").unwrap();
    let blobs = Blob::list(&client, &vhds.name, &LIST_BLOB_OPTIONS_DEFAULT).unwrap();
    let blob = blobs.iter().find(|ref x| x.name == "go_rust12.txt").unwrap();

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

    let mut lbo = LEASE_BLOB_OPTIONS_DEFAULT.clone();
    lbo.lease_duration = Some(30);
    let ret = blob.lease(client, LeaseAction::Acquire, &lbo).unwrap();
    println!("ret == {:?}", ret);

}

#[allow(dead_code)]
fn list_blobs(client: &Client) {
    println!("running list_blobs");

    let mut lbo2 = LIST_BLOB_OPTIONS_DEFAULT.clone();
    lbo2.max_results = 15;

    loop {
        let uc = Blob::list(&client, "rust", &lbo2).unwrap();

        println!("uc {:?}\n\n", uc);

        if !uc.is_complete() {
            lbo2.next_marker = Some(uc.next_marker().unwrap().to_owned());
        } else {
            break;
        }
    }
}

#[allow(dead_code)]
fn list_containers(client: &Client) {
    println!("running list_containers");

    let mut lco = LIST_CONTAINER_OPTIONS_DEFAULT.clone();
    lco.max_results = 2;
    loop {
        let ret = Container::list(&client, &lco).unwrap();

        println!("ret {:?}\n\n", ret);

        if !ret.is_complete() {
            lco.next_marker = Some(ret.next_marker().unwrap().to_owned());
        } else {
            break;
        }
    }
}

#[allow(dead_code)]
fn put_block_blob(client: &Client) {
    use std::fs::metadata;
    use std::fs::File;

    println!("\nrunning put_block_blob");

    let blob_name: &'static str = "Win64OpenSSL-1_0_2e.exe";
    let file_name: &'static str = "C:\\temp\\Win64OpenSSL-1_0_2e.exe";
    let container_name: &'static str = "rust";
    let metadata = metadata(file_name).unwrap();
    let mut file = File::open(file_name).unwrap();

    let content_length = metadata.len();

    {
        let containers = Container::list(&client, &LIST_CONTAINER_OPTIONS_DEFAULT).unwrap();

        let cont = containers.iter().find(|x| x.name == container_name);
        if let None = cont {
            Container::create(client, container_name, PublicAccess::Blob).unwrap();
        }
    }

    let new_blob = Blob {
        name: blob_name.to_owned(),
        container_name: container_name.to_owned(),
        snapshot_time: None,
        last_modified: UTC::now(),
        etag: "".to_owned(),
        content_length: content_length,
        content_type: "application/octet-stream".parse::<Mime>().unwrap(),
        content_encoding: None,
        content_language: None,
        content_md5: None,
        cache_control: None,
        x_ms_blob_sequence_number: None,
        blob_type: BlobType::BlockBlob,
        lease_status: LeaseStatus::Unlocked,
        lease_state: LeaseState::Available,
        lease_duration: None,
        copy_id: None,
        copy_status: None,
        copy_source: None,
        copy_progress: None,
        copy_completion: None,
        copy_status_description: None,
    };

    new_blob.put_block(&client,
                       "block_name",
                       &PUT_BLOCK_OPTIONS_DEFAULT,
                       (&mut file, 1024 * 1024))
            .unwrap();

    println!("created {:?}", new_blob);
}

#[allow(dead_code)]
fn put_page_blob(client: &Client) {
    use std::fs::metadata;
    use std::fs::File;

    println!("\nrunning put_page_blob");

    let blob_name: &'static str = "MindDB_Log.ldf";
    let file_name: &'static str = "C:\\temp\\MindDB_Log.ldf";
    let container_name: &'static str = "rust";
    let metadata = metadata(file_name).unwrap();
    let mut file = File::open(file_name).unwrap();

    {
        let containers = Container::list(&client, &LIST_CONTAINER_OPTIONS_DEFAULT).unwrap();

        let cont = containers.iter().find(|x| x.name == container_name);
        if let None = cont {
            Container::create(client, container_name, PublicAccess::Blob).unwrap();
        }
    }

    // align to 512 bytes
    let content_length = metadata.len() % 512 + metadata.len();

    let new_blob = Blob {
        name: blob_name.to_owned(),
        container_name: container_name.to_owned(),
        snapshot_time: None,
        last_modified: UTC::now(),
        etag: "".to_owned(),
        content_length: content_length,
        content_type: "application/octet-stream".parse::<Mime>().unwrap(),
        content_encoding: None,
        content_language: None,
        content_md5: None,
        cache_control: None,
        x_ms_blob_sequence_number: None,
        blob_type: BlobType::PageBlob,
        lease_status: LeaseStatus::Unlocked,
        lease_state: LeaseState::Available,
        lease_duration: None,
        copy_id: None,
        copy_status: None,
        copy_source: None,
        copy_progress: None,
        copy_completion: None,
        copy_status_description: None,
    };

    new_blob.put(&client, &PUT_OPTIONS_DEFAULT, None)
            .unwrap();

    let range = BA512Range::new(0, 1024 * 1024 - 1).unwrap();

    new_blob.put_page(client,
                      &range, // 1MB
                      &PUT_PAGE_OPTIONS_DEFAULT,
                      (&mut file, range.size()))
            .unwrap();

    println!("created {:?}", new_blob);
}
<|endoftext|>


<commit_before>#![feature(phase)]
extern crate serialize;
#[phase(plugin)] extern crate docopt_macros;
extern crate docopt;
extern crate core;
extern crate collections;
extern crate regex;
#[phase(plugin)] extern crate regex_macros;

use docopt::FlagParser;
use std::io::BufferedReader;
use std::io::BufferedWriter;
use std::io::File;
use core::result::Result;
use collections::string::String;
use collections::vec::Vec;
use collections::PriorityQueue;

// Interleaver takes N files (e.g., i_1.txt ... i_N.txt) and transforms them into N output files
// named o_1.txt ... o_N.txt.  The lines of the input files must be prefixed with time stamps.  The
// output files contain the same content as the input files but only one file is allowed to make
// progress on a given line.  The other files are empty.

docopt!(Args, "
Usage:
  interleaver <file>...

Options:
  -h, --help    Show this screen.
", )

type BrType = BufferedReader<core::result::Result<std::io::fs::File,std::io::IoError>>;
type BwType = BufferedWriter<core::result::Result<std::io::fs::File,std::io::IoError>>;

#[deriving(Show,PartialEq,PartialOrd,Eq,Ord)]
enum Month { Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec }

fn string_to_month (s : &str) -> Option<Month> {
  match s {
    "Jan" => Some (Jan),
    "Feb" => Some (Feb),
    "Mar" => Some (Mar),
    "Apr" => Some (Apr),
    "May" => Some (May),
    "Jun" => Some (Jun),
    "Jul" => Some (Jul),
    "Aug" => Some (Aug),
    "Sep" => Some (Sep),
    "Oct" => Some (Oct),
    "Nov" => Some (Nov),
    "Dec" => Some (Dec),
    _     => None
  }
}

#[deriving(Show,PartialEq,Eq)]
struct TimedLine {
  l       : String,
  month   : Month,
  day     : u8,
  hour    : u8,
  minute  : u8,
  second  : u8,
  usecond : u32,
  target  : uint
}

impl TimedLine {
  fn new (l : &String, target : uint) -> TimedLine {
    let re = regex! (r"^(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) +(\d+) (\d+):(\d+):(\d+) (\d+):.*$");
    let caps = match re.captures (l.as_slice()) {
      None => fail! ("Could not analyze line: \"{}\"", l),
      Some (caps) => caps
    };
    let (month, day, hour, minute, second, usecond) =
      match (string_to_month (caps.at(1)), from_str::<u8>(caps.at(2)), from_str::<u8> (caps.at(3)), from_str::<u8> (caps.at(4)), from_str::<u8> (caps.at(5)), from_str::<u32> (caps.at(6))
      ) {
        (Some (month), Some (day), Some (hour), Some (minute), Some (second), Some (usecond)) => (month, day, hour, minute, second, usecond*100),
        _ => fail! ("Could not extract fields from line: \"{}\"", l)
      };

    TimedLine {
      l       : l.clone (),
      month   : month,
      day     : day,
      hour    : hour,
      minute  : minute,
      second  : second,
      usecond : usecond,
      target  : target
    }
  }
}

impl Ord for TimedLine {
  fn cmp(&self, other: &TimedLine) -> Ordering {
    let month_cmp = self.month.cmp (&other.month);
    if month_cmp == Equal {
      let day_cmp = self.day.cmp (&other.day);
      if day_cmp == Equal {
        let hour_cmp = self.hour.cmp (&other.hour);
        if hour_cmp == Equal {
          let minute_cmp = self.minute.cmp (&other.minute);
          if minute_cmp == Equal {
            let second_cmp = self.second.cmp (&other.second);
            if second_cmp == Equal {
              self.usecond.cmp (&other.usecond)
            }
            else { second_cmp }
          }
          else { minute_cmp }
        }
        else { hour_cmp }
      }
      else { day_cmp }
    }
    else { month_cmp }
  }
}

impl PartialOrd for TimedLine {
  fn partial_cmp(&self, other: &TimedLine) -> Option<Ordering> {
    Some (self.cmp (other))
  }
}

struct TimedLineQueue<'a> {
  q : PriorityQueue<TimedLine>,
  readers : &'a mut Vec<BrType>
}

impl<'a> TimedLineQueue<'a> {
  fn fill_que (&mut self, target : uint) {
    let reader = self.readers.get_mut (target);
    let mut lines = reader.lines ();
    let line_opt = lines.next ();
    match line_opt {
      Some (line) => {
        let line = &line.unwrap();
        let timed_line = TimedLine::new (line, target);
        self.q.push (timed_line);
      }
      None => ()
    }
  }

  fn new (readers : & 'a mut Vec<BrType>) -> TimedLineQueue {
    let mut res = TimedLineQueue {
      q : PriorityQueue::new (),
      readers : readers
    };

    for (i, _) in res.readers.mut_iter().enumerate () {
      res.fill_que(i);
    }

    res
  }
}

impl<'a> Iterator<TimedLine> for TimedLineQueue<'a> {
  fn next (&mut self) -> Option<TimedLine> {
    let timed_line_opt = self.q.pop ();
    match timed_line_opt {
      None => (),
      Some (ref timed_line) => {
        self.fill_que(timed_line.target);
      }
    }

    timed_line_opt
  }
}

fn main() {
  let args: Args = FlagParser::parse().unwrap_or_else(|e| e.exit());

  // let s = String::from_str ("Sep  2 14:25:02 8993: (main|info): --- NODE STARTED ---");
  // let tl = TimedLine::new (&s, 5u);
  // println!("TimedLine: {}", tl);

  // println!("Starting {}", args);

  let file_names = args.arg_file;
  let file_count = file_names.len ();
  let paths_it = file_names.iter ().map (|n| Path::new(n.clone ()));
  let files_it = paths_it.map (|path| File::open(&path));
  let mut buffered_readers_it = files_it.map (|file| BufferedReader::new (file));
  let mut buffered_readers_vec : Vec<BrType> = buffered_readers_it.collect ();

  let paths_out_it = range (0u, file_names.len ()).map (|i| Path::new (i.to_string()));
  let files_out_it = paths_out_it.map (|path_out| {
    let res = File::create(&path_out);
    match res {
      Ok(_)  => (),
      Err(e) => fail!("Could not open output file: {}", e)
    };
    res
  }
  );
  let mut buffered_writers_it = files_out_it.map (|file_out| BufferedWriter::new (file_out) );

  let mut buffered_writers_vec : Vec<BwType> = buffered_writers_it.collect ();

  let mut tlq = TimedLineQueue::new (& mut buffered_readers_vec);
  for timed_line in tlq {
    let target = timed_line.target;
    {
      let writer = buffered_writers_vec.get_mut (target);
      let res = writer.write_line (format!("{}", timed_line).as_slice ());
      match res {
        Ok(_) => (),
        Err(e) => {
          let ref file_name = file_names[target];
          fail! ("Could not write to {}: {}", file_name, e);
        }
      };
    }

    for i in range (0, file_count).filter (|i| *i != target) {
      let writer = buffered_writers_vec.get_mut (i);
      let res = writer.write_line ("");
      match res {
        Ok(_) => (),
        Err(e) => {
          let ref file_name = file_names[i];
          fail! ("Could not write to {}: {}", file_name, e);
        }
      };
    }
  }
}
<commit_msg>Strip newlines from input<commit_after>#![feature(phase)]
extern crate serialize;
#[phase(plugin)] extern crate docopt_macros;
extern crate docopt;
extern crate core;
extern crate collections;
extern crate regex;
#[phase(plugin)] extern crate regex_macros;

use docopt::FlagParser;
use std::io::BufferedReader;
use std::io::BufferedWriter;
use std::io::File;
use core::result::Result;
use collections::string::String;
use collections::vec::Vec;
use collections::PriorityQueue;

// Interleaver takes N files (e.g., i_1.txt ... i_N.txt) and transforms them into N output files
// named o_1.txt ... o_N.txt.  The lines of the input files must be prefixed with time stamps.  The
// output files contain the same content as the input files but only one file is allowed to make
// progress on a given line.  The other files are empty.

docopt!(Args, "
Usage:
  interleaver <file>...

Options:
  -h, --help    Show this screen.
", )

type BrType = BufferedReader<core::result::Result<std::io::fs::File,std::io::IoError>>;
type BwType = BufferedWriter<core::result::Result<std::io::fs::File,std::io::IoError>>;

#[deriving(Show,PartialEq,PartialOrd,Eq,Ord)]
enum Month { Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec }

fn string_to_month (s : &str) -> Option<Month> {
  match s {
    "Jan" => Some (Jan),
    "Feb" => Some (Feb),
    "Mar" => Some (Mar),
    "Apr" => Some (Apr),
    "May" => Some (May),
    "Jun" => Some (Jun),
    "Jul" => Some (Jul),
    "Aug" => Some (Aug),
    "Sep" => Some (Sep),
    "Oct" => Some (Oct),
    "Nov" => Some (Nov),
    "Dec" => Some (Dec),
    _     => None
  }
}

#[deriving(Show,PartialEq,Eq)]
struct TimedLine {
  l       : String,
  month   : Month,
  day     : u8,
  hour    : u8,
  minute  : u8,
  second  : u8,
  usecond : u32,
  target  : uint
}

impl TimedLine {
  fn new (l : &str, target : uint) -> TimedLine {
    let re = regex! (r"^(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) +(\d+) (\d+):(\d+):(\d+) (\d+):.*$");
    let caps = match re.captures (l) {
      None => fail! ("Could not analyze line: \"{}\"", l),
      Some (caps) => caps
    };
    let (month, day, hour, minute, second, usecond) =
      match (string_to_month (caps.at(1)), from_str::<u8>(caps.at(2)), from_str::<u8> (caps.at(3)), from_str::<u8> (caps.at(4)), from_str::<u8> (caps.at(5)), from_str::<u32> (caps.at(6))
      ) {
        (Some (month), Some (day), Some (hour), Some (minute), Some (second), Some (usecond)) => (month, day, hour, minute, second, usecond*100),
        _ => fail! ("Could not extract fields from line: \"{}\"", l)
      };

    TimedLine {
      l       : String::from_str (l),
      month   : month,
      day     : day,
      hour    : hour,
      minute  : minute,
      second  : second,
      usecond : usecond,
      target  : target
    }
  }
}

impl Ord for TimedLine {
  fn cmp(&self, other: &TimedLine) -> Ordering {
    let month_cmp = self.month.cmp (&other.month);
    if month_cmp == Equal {
      let day_cmp = self.day.cmp (&other.day);
      if day_cmp == Equal {
        let hour_cmp = self.hour.cmp (&other.hour);
        if hour_cmp == Equal {
          let minute_cmp = self.minute.cmp (&other.minute);
          if minute_cmp == Equal {
            let second_cmp = self.second.cmp (&other.second);
            if second_cmp == Equal {
              self.usecond.cmp (&other.usecond)
            }
            else { second_cmp }
          }
          else { minute_cmp }
        }
        else { hour_cmp }
      }
      else { day_cmp }
    }
    else { month_cmp }
  }
}

impl PartialOrd for TimedLine {
  fn partial_cmp(&self, other: &TimedLine) -> Option<Ordering> {
    Some (self.cmp (other))
  }
}

struct TimedLineQueue<'a> {
  q : PriorityQueue<TimedLine>,
  readers : &'a mut Vec<BrType>
}

impl<'a> TimedLineQueue<'a> {
  fn fill_que (&mut self, target : uint) {
    let reader = self.readers.get_mut (target);
    let mut lines = reader.lines ();
    let line_opt = lines.next ();
    match line_opt {
      Some (line) => {
        let line = line.unwrap();
        let line = line.as_slice();
        let line = line.trim_chars('\n');
        let timed_line = TimedLine::new (line, target);
        self.q.push (timed_line);
      }
      None => ()
    }
  }

  fn new (readers : & 'a mut Vec<BrType>) -> TimedLineQueue {
    let mut res = TimedLineQueue {
      q : PriorityQueue::new (),
      readers : readers
    };

    for (i, _) in res.readers.mut_iter().enumerate () {
      res.fill_que(i);
    }

    res
  }
}

impl<'a> Iterator<TimedLine> for TimedLineQueue<'a> {
  fn next (&mut self) -> Option<TimedLine> {
    let timed_line_opt = self.q.pop ();
    match timed_line_opt {
      None => (),
      Some (ref timed_line) => {
        self.fill_que(timed_line.target);
      }
    }

    timed_line_opt
  }
}

fn main() {
  let args: Args = FlagParser::parse().unwrap_or_else(|e| e.exit());

  // let s = String::from_str ("Sep  2 14:25:02 8993: (main|info): --- NODE STARTED ---");
  // let tl = TimedLine::new (&s, 5u);
  // println!("TimedLine: {}", tl);

  // println!("Starting {}", args);

  let file_names = args.arg_file;
  let file_count = file_names.len ();
  let paths_it = file_names.iter ().map (|n| Path::new(n.clone ()));
  let files_it = paths_it.map (|path| File::open(&path));
  let mut buffered_readers_it = files_it.map (|file| BufferedReader::new (file));
  let mut buffered_readers_vec : Vec<BrType> = buffered_readers_it.collect ();

  let paths_out_it = range (0u, file_names.len ()).map (|i| Path::new (format!("{}.txt", i)));
  let files_out_it = paths_out_it.map (|path_out| {
    let res = File::create(&path_out);
    match res {
      Ok(_)  => (),
      Err(e) => fail!("Could not open output file: {}", e)
    };
    res
  }
  );
  let mut buffered_writers_it = files_out_it.map (|file_out| BufferedWriter::new (file_out) );

  let mut buffered_writers_vec : Vec<BwType> = buffered_writers_it.collect ();

  let mut tlq = TimedLineQueue::new (& mut buffered_readers_vec);
  for timed_line in tlq {
    let target = timed_line.target;
    {
      let writer = buffered_writers_vec.get_mut (target);
      let res = writer.write_line (format!("{}", timed_line.l).as_slice ());
      match res {
        Ok(_) => (),
        Err(e) => {
          let ref file_name = file_names[target];
          fail! ("Could not write to {}: {}", file_name, e);
        }
      };
    }

    for i in range (0, file_count).filter (|i| *i != target) {
      let writer = buffered_writers_vec.get_mut (i);
      let res = writer.write_line ("");
      match res {
        Ok(_) => (),
        Err(e) => {
          let ref file_name = file_names[i];
          fail! ("Could not write to {}: {}", file_name, e);
        }
      };
    }
  }
}
<|endoftext|>


<commit_before>#![cfg_attr(feature="clippy", feature(plugin))]
#![cfg_attr(feature="clippy", plugin(clippy))]
#![feature(custom_derive, plugin)]
#![plugin(serde_macros)]

#[macro_use]
extern crate clap;
extern crate requests;
extern crate serde;
extern crate serde_json;

use clap::{App, SubCommand, AppSettings, ArgMatches};
mod crates;

const CARGO: &'static str = "cargo";

#[derive(Debug, PartialEq)]
enum Flag {
    Repository,
    Documentation,
    Downloads,
    Homepage,
    Default,
}

#[derive(Debug)]
struct Report {
    flags: Vec<Flag>,
    verbose: bool,
}

impl Report {
    pub fn new(info: &ArgMatches) -> Self {

        let mut flags: Vec<Flag> = vec![];
        if info.is_present("repository") {
            flags.push(Flag::Repository);
        }
        if info.is_present("documentation") {
            flags.push(Flag::Documentation);
        }
        if info.is_present("downloads") {
            flags.push(Flag::Downloads);
        }
        if info.is_present("homepage") {
            flags.push(Flag::Homepage);
        }

        if flags.is_empty() {
            flags.push(Flag::Default);
        }

        Report {
            flags: flags,
            verbose: info.is_present("verbose"),
        }
    }

    pub fn report(&self, name: &str) {
        if let Some(krate) = query(name).map(|r| r.krate) {
            for flag in &self.flags {
                match *flag {
                    Flag::Repository => krate.print_repository(self.verbose),
                    Flag::Documentation => krate.print_documentation(self.verbose),
                    Flag::Downloads => krate.print_downloads(self.verbose),
                    Flag::Homepage => krate.print_homepage(self.verbose),
                    Flag::Default => reportv(&krate, self.verbose),
                }
            }
        }
    }
}

fn reportv(krate: &crates::Crate, verbose: bool) {
    if verbose {
        println!("{:#}", krate);
    } else {
        println!("{}", krate);
    }
}

fn query(krate: &str) -> Option<crates::Reply> {
    let response = requests::get(&format!("http://crates.io/api/v1/crates/{}", krate)).unwrap();
    // println!("{:?}", response.json().unwrap());
    response.from_json::<crates::Reply>()
}

// use std::fmt;
// fn debug<T>(item: &T)
//     where T: fmt::Debug
// {
//     println!("{:#?}", item);
// }

fn main() {

    let matches = App::new(CARGO)
        .bin_name(CARGO)
        .author(crate_authors!())
        .version(crate_version!())
        .about("Query crates.io registry for crates details")
        .setting(AppSettings::GlobalVersion)
        .setting(AppSettings::VersionlessSubcommands)
        .setting(AppSettings::SubcommandRequiredElseHelp)
        .subcommand(SubCommand::with_name("info")
            .setting(AppSettings::ArgRequiredElseHelp)
            .setting(AppSettings::TrailingVarArg)
            .arg_from_usage("-d, --documentation 'Report documentation URL'")
            .arg_from_usage("-D, --downloads 'Report number of crate downloads'")
            .arg_from_usage("-H, --homepage 'Report home page URL'")
            .arg_from_usage("-r, --repository 'Report crate repository URL'")
            .arg_from_usage("-v, --verbose 'Report more details'")
            .arg_from_usage("<crate>... 'crate to query'"))
        .get_matches();

    if let Some(info) = matches.subcommand_matches("info") {
        if let Some(crates) = info.values_of("crate") {
            let rep = Report::new(info);
            for krate in crates {
                // debug(&krate);
                rep.report(krate);
            }
        }
    }
}
<commit_msg>Add Json flag<commit_after>#![cfg_attr(feature="clippy", feature(plugin))]
#![cfg_attr(feature="clippy", plugin(clippy))]
#![feature(custom_derive, plugin)]
#![plugin(serde_macros)]

#[macro_use]
extern crate clap;
extern crate requests;
extern crate serde;
extern crate serde_json;

use clap::{App, SubCommand, AppSettings, ArgMatches};
mod crates;

const CARGO: &'static str = "cargo";

#[derive(Debug, PartialEq)]
enum Flag {
    Repository,
    Documentation,
    Downloads,
    Homepage,
    Json,
    Default,
}

#[derive(Debug)]
struct Report {
    flags: Vec<Flag>,
    verbose: bool,
}

impl Report {
    pub fn new(info: &ArgMatches) -> Self {

        let mut flags: Vec<Flag> = vec![];
        if info.is_present("repository") {
            flags.push(Flag::Repository);
        }
        if info.is_present("documentation") {
            flags.push(Flag::Documentation);
        }
        if info.is_present("downloads") {
            flags.push(Flag::Downloads);
        }
        if info.is_present("homepage") {
            flags.push(Flag::Homepage);
        }

        if info.is_present("json") {
            flags.push(Flag::Json);
        }

        if flags.is_empty() {
            flags.push(Flag::Default);
        }

        Report {
            flags: flags,
            verbose: info.is_present("verbose"),
        }
    }

    pub fn report(&self, name: &str) {
        if let Some(krate) = query(name).map(|r| r.krate) {
            for flag in &self.flags {
                match *flag {
                    Flag::Repository => krate.print_repository(self.verbose),
                    Flag::Documentation => krate.print_documentation(self.verbose),
                    Flag::Downloads => krate.print_downloads(self.verbose),
                    Flag::Homepage => krate.print_homepage(self.verbose),
                    Flag::Json => {},
                    Flag::Default => reportv(&krate, self.verbose),
                }
            }
        }
    }
}

fn reportv(krate: &crates::Crate, verbose: bool) {
    if verbose {
        println!("{:#}", krate);
    } else {
        println!("{}", krate);
    }
}

fn query(krate: &str) -> Option<crates::Reply> {
    let response = requests::get(&format!("http://crates.io/api/v1/crates/{}", krate)).unwrap();
    // println!("{:?}", response.json().unwrap());
    response.from_json::<crates::Reply>()
}

// use std::fmt;
// fn debug<T>(item: &T)
//     where T: fmt::Debug
// {
//     println!("{:#?}", item);
// }

fn main() {

    let matches = App::new(CARGO)
        .bin_name(CARGO)
        .author(crate_authors!())
        .version(crate_version!())
        .about("Query crates.io registry for crates details")
        .setting(AppSettings::GlobalVersion)
        .setting(AppSettings::VersionlessSubcommands)
        .setting(AppSettings::SubcommandRequiredElseHelp)
        .subcommand(SubCommand::with_name("info")
            .setting(AppSettings::ArgRequiredElseHelp)
            .setting(AppSettings::TrailingVarArg)
            .arg_from_usage("-d, --documentation 'Report documentation URL'")
            .arg_from_usage("-D, --downloads 'Report number of crate downloads'")
            .arg_from_usage("-H, --homepage 'Report home page URL'")
            .arg_from_usage("-r, --repository 'Report crate repository URL'")
            .arg_from_usage("-v, --verbose 'Report more details'")
            .arg_from_usage("<crate>... 'crate to query'"))
        .get_matches();

    if let Some(info) = matches.subcommand_matches("info") {
        if let Some(crates) = info.values_of("crate") {
            let rep = Report::new(info);
            for krate in crates {
                // debug(&krate);
                rep.report(krate);
            }
        }
    }
}
<|endoftext|>


<commit_before>extern crate clap;
extern crate chrono;

use std::env;
use std::fs::DirBuilder;
use std::fs::File;
use std::fs::OpenOptions;
use std::io::Write;
use std::io::Read;
use std::io;
use std::io::Seek;
use std::io::SeekFrom;
use std::ops::Sub;
use std::ops::Add;
use std::path::PathBuf;
use std::process;
use std::str;

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

use chrono::DateTime;
use chrono::Timelike;
use chrono::TimeZone;
use chrono::UTC;
use chrono::date::Date;

const RECORD_LENGTH: usize = 22;

#[derive(Debug)]
#[derive(PartialEq)]
enum Action {
	PunchIn,
	PunchOut,
	Unset
}

struct Record {
	timestamp: DateTime<UTC>,
	action: Action
}

fn main() {
    match ensure_log_file_exists() {
    	Ok(_) => {},
    	Err(e) => {
    		println!("Couldn't create punch log: {}.\nExiting.", e);
			process::exit(1)
    	}
    }

    let args = App::new("Punch").
	    about("A simple time tracker app").
	    version("0.1").
        setting(AppSettings::ArgRequiredElseHelp).
        subcommand(SubCommand::with_name("in").about("Punch in")).
        subcommand(SubCommand::with_name("out").about("Punch out")).
        subcommand(SubCommand::with_name("card").about("Display state").
	        arg(Arg::with_name("week").long("week").short("w").help("Display summary for the last week")).
	        arg(Arg::with_name("mtd").long("mtd").short("m").help("Display summary for the month to date"))).
        get_matches();

	match args.subcommand() {
		("card", Some(specifier)) => {
			if specifier.is_present("week") {
				print_weekly_summary()
			}
			else if specifier.is_present("mtd") {
				println!("Print month-to-date summary")
			}
			else {
				print_current_state();
			}
		},
		("in", _) => {
			ensure_last_record_is_of_action(Action::PunchOut);
			write_record_to_log(chrono::UTC::now(), Action::PunchIn);
		},
		("out", _) => {
			ensure_last_record_is_of_action(Action::PunchIn);
			write_record_to_log(chrono::UTC::now(), Action::PunchOut);
		},
		_ => {
			println!("Unknown command")
		}
	}
}

fn write_record_to_log(tm: DateTime<UTC>, action: Action) {
	let action_token = match action {
		Action::PunchIn => "I",
		Action::PunchOut => "O",
		Action::Unset => "U"
	};

    let mut config_file = get_conf_file(false, true).unwrap();
    let fmt = tm.format("%FT%T");
	let formatted_timestamp = fmt.to_string();
	append_to_file(format!("{}_{}\n", formatted_timestamp, action_token).as_bytes(), &mut config_file);
}

fn print_weekly_summary() {
	let start_of_week = chrono::UTC::now().with_second(0).
		map(|ts| ts.with_minute(0).
			map(|ts| ts.with_hour(0))
		).unwrap().unwrap().unwrap();

		print_daily_durations_since(start_of_week);
}

fn print_daily_durations_since(start_time: chrono::DateTime<UTC>) {
	let mut daily_durations: Vec<DailyDuration> = vec![];
	let mut record_offset = 0;
	let mut record = empty_record();
	let mut config_file = get_conf_file(true, false).unwrap();
	let &mut current_date: &mut chrono::Date<UTC> =
		&mut chrono::UTC::now().date().add(chrono::Duration::days(1));

	let mut day_count: i64 = 0;

	loop {
		populate_record_at_offset_from_end(&mut config_file, &mut record, record_offset);
		if record.timestamp.date() != current_date && day_count != 0 {

			daily_durations.push(DailyDuration {
					date: current_date,
					duration: chrono::Duration::days(1)
			});
			day_count += 1;
		}

		record_offset -= 1;
	}
}

struct DailyDuration {
	date: chrono::date::Date<UTC>,
	duration: chrono::Duration
}

fn print_current_state() {
    let mut config_file = get_conf_file(true, false).unwrap();
    let mut record = empty_record();

    match populate_record_at_offset_from_end(&mut config_file, &mut record, 0) {
    	Ok(_) => {},
    	Err(e) => {
    		println!("Couldn't read entry: {}.\nExiting.", e);
			process::exit(1)
    	}
    }

    if record.action == Action::PunchIn {
    	let current_timestamp = chrono::UTC::now();
    	let time_punched_in = current_timestamp.sub(record.timestamp);
    	println!("Punched in since {} ({})", record.timestamp, format_duration(time_punched_in))
    }
    else {
    	let mut previous_record = empty_record();
    	match populate_record_at_offset_from_end(&mut config_file, &mut previous_record, 1) {
	    	Ok(_) => {},
	    	Err(e) => {
	    		println!("Couldn't read entry: {}.\nExiting.", e);
				process::exit(1)
	    	}
	    }

    	let delta = record.timestamp.sub(previous_record.timestamp);
    	println!("Previously punched in between {} and {} ({})",
    		previous_record.timestamp, record.timestamp, format_duration(delta))
    }
}

fn format_duration(duration: chrono::Duration) -> String {
	format!("{:02}h{:02}m", duration.num_hours(), duration.num_minutes())
}

fn ensure_last_record_is_of_action(expected_action: Action) {
	let mut config_file = get_conf_file(true, false).unwrap();
    let mut record = empty_record();

    match populate_record_at_offset_from_end(&mut config_file, &mut record, 0) {
    	Ok(_) => {},
    	Err(e) => {
    		println!("Couldn't create punch log: {}.\nExiting.", e);
			process::exit(1)
    	}
    }

    if record.action != expected_action {
    	match expected_action {
    		Action::PunchIn => {
    			println!("Already punched out, punch in first!");
				process::exit(0)
    		}
    		Action::PunchOut => {
    			println!("Already punched in, punch out first!");
    			process::exit(0)
    		}
    		Action::Unset => {
    			println!("Found unset action, log file corrupt");
    			process::exit(1)
    		}
    	}
    }
}

fn get_conf_file(read: bool, append: bool) -> io::Result<File> {
	let mut conf_file = PathBuf::new();
    conf_file.push(env::home_dir().unwrap());
    conf_file.push(".punch");
    conf_file.push("punch.log");

    OpenOptions::new().read(read).append(append).open(conf_file)
}

fn empty_record() -> Record {
	Record {
    	action: Action::Unset,
    	timestamp: chrono::UTC::now()
    }
}

fn populate_record_at_offset_from_end(config_file: &mut File, record: &mut Record, offset_from_end: u64) -> Result<(), String> {
	return seek_to_record_offset(config_file, offset_from_end).
		and_then(|_| populate_record_at_current_offset(config_file, record))
}

fn populate_record_at_current_offset(f: &mut File, record: &mut Record) -> Result<(), String> {
	let mut data = [0 as u8; RECORD_LENGTH];
	let read = f.read(&mut data);
	if read.unwrap() != RECORD_LENGTH {
		panic!("Could not read complete record of {} bytes", RECORD_LENGTH)
	}
	let (ts_data, rest) = data.split_at(19);
	let timestamp = str::from_utf8(&ts_data).unwrap();
	let parse_result = chrono::UTC.datetime_from_str(&timestamp, "%FT%T");

	let record_ts = parse_result.unwrap().with_timezone(&chrono::UTC);
	record.timestamp = record_ts;
	let action_string = str::from_utf8(&rest).unwrap();
	if action_string == "_O\n" {
		record.action = Action::PunchOut;
	}
	else if action_string == "_I\n" {
		record.action = Action::PunchIn;
	}
	else {
		return Err(format!("Could not determine action type from '{}'", action_string))
	}
	Ok(())
}

fn seek_to_record_offset(f: &mut File, record_offset: u64) -> Result<(), String> {
	let m = f.metadata().unwrap();
	let file_len = m.len();

	if file_len < RECORD_LENGTH as u64 {
		return Err(String::from("No data in log - punch in first!"))
	}

	let record_length_in_bytes = RECORD_LENGTH as u64;
	let seek_offset = file_len - ((record_offset + 1) * record_length_in_bytes);

	if f.seek(SeekFrom::Start(seek_offset)).unwrap() != seek_offset {
		return Err(format!("Could not seek to record offset {}", seek_offset))
	}
	Ok(())
}

fn append_to_file(data: &[u8], f: &mut File) {
	match f.write_all(data) {
    	Ok(_) => {},
    	Err(e) => println!("Failed to write data to log: {}", e)
    }
}

fn ensure_log_file_exists() -> io::Result<()> {
    let mut conf_dir = PathBuf::new();
    conf_dir.push(env::home_dir().unwrap());
    conf_dir.push(".punch");
    let config_path = conf_dir.as_path();

    let mut conf_file_builder = PathBuf::from(config_path);
    conf_file_builder.push("punch.log");

    let mut dir_builder = DirBuilder::new();
    dir_builder.recursive(true);

    try!(dir_builder.create(config_path));

    let conf_file = conf_file_builder.as_path();
    match OpenOptions::new().create(true).write(true).open(conf_file) {
    	Ok(_) => Ok(()),
    	Err(e) => Err(e)
    }
}<commit_msg>more checking<commit_after>extern crate clap;
extern crate chrono;

use std::env;
use std::fs::DirBuilder;
use std::fs::File;
use std::fs::OpenOptions;
use std::io::Write;
use std::io::Read;
use std::io;
use std::io::Seek;
use std::io::SeekFrom;
use std::ops::Sub;
use std::ops::Add;
use std::path::PathBuf;
use std::process;
use std::str;

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

use chrono::DateTime;
use chrono::Timelike;
use chrono::TimeZone;
use chrono::UTC;
use chrono::date::Date;

const RECORD_LENGTH: usize = 22;

#[derive(Debug)]
#[derive(PartialEq)]
enum Action {
	PunchIn,
	PunchOut,
	Unset
}

struct Record {
	timestamp: DateTime<UTC>,
	action: Action
}

fn main() {
    match ensure_log_file_exists() {
    	Ok(_) => {},
    	Err(e) => {
    		println!("Couldn't create punch log: {}.\nExiting.", e);
			process::exit(1)
    	}
    }

    let args = App::new("Punch").
	    about("A simple time tracker app").
	    version("0.1").
        setting(AppSettings::ArgRequiredElseHelp).
        subcommand(SubCommand::with_name("in").about("Punch in")).
        subcommand(SubCommand::with_name("out").about("Punch out")).
        subcommand(SubCommand::with_name("card").about("Display state").
	        arg(Arg::with_name("week").long("week").short("w").help("Display summary for the last week")).
	        arg(Arg::with_name("mtd").long("mtd").short("m").help("Display summary for the month to date"))).
        get_matches();

	match args.subcommand() {
		("card", Some(specifier)) => {
			if specifier.is_present("week") {
				print_weekly_summary()
			}
			else if specifier.is_present("mtd") {
				println!("Print month-to-date summary")
			}
			else {
				print_current_state();
			}
		},
		("in", _) => {
			ensure_last_record_is_of_action(Action::PunchOut);
			write_record_to_log(chrono::UTC::now(), Action::PunchIn);
		},
		("out", _) => {
			ensure_last_record_is_of_action(Action::PunchIn);
			write_record_to_log(chrono::UTC::now(), Action::PunchOut);
		},
		_ => {
			println!("Unknown command")
		}
	}
}

fn write_record_to_log(tm: DateTime<UTC>, action: Action) {
	let action_token = match action {
		Action::PunchIn => "I",
		Action::PunchOut => "O",
		Action::Unset => "U"
	};

    let mut config_file = get_conf_file(false, true).unwrap();
    let fmt = tm.format("%FT%T");
	let formatted_timestamp = fmt.to_string();
	append_to_file(format!("{}_{}\n", formatted_timestamp, action_token).as_bytes(), &mut config_file);
}

fn print_weekly_summary() {
	let start_of_week = chrono::UTC::now().with_second(0).
		map(|ts| ts.with_minute(0).
			map(|ts| ts.with_hour(0))
		).unwrap().unwrap().unwrap();

		print_daily_durations_since(start_of_week);
}

fn print_daily_durations_since(start_time: chrono::DateTime<UTC>) {
	let mut daily_durations: Vec<DailyDuration> = vec![];
	let mut record_offset = 0;
	let mut record = empty_record();
	let mut config_file = get_conf_file(true, false).unwrap();
	let &mut current_date: &mut chrono::Date<UTC> =
		&mut chrono::UTC::now().date().add(chrono::Duration::days(1));

	let mut day_count: i64 = 0;
	let mut total_seconds_in_current_day: i64 = 0;

	// TODO need to account for duration between now and last punch-in
	if get_last_record_action() == Action::PunchIn {
		record_offset = 1
	}

	loop {
		populate_record_at_offset_from_end(&mut config_file, &mut record, record_offset);
		if record.timestamp.date() != current_date && day_count != 0 {

			daily_durations.push(DailyDuration {
					date: current_date,
					duration: chrono::Duration::seconds(total_seconds_in_current_day)
			});
			day_count += 1;
			total_seconds_in_current_day = 0;
		}

		record_offset -= 1;
	}
}

struct DailyDuration {
	date: chrono::date::Date<UTC>,
	duration: chrono::Duration
}

fn print_current_state() {
    let mut config_file = get_conf_file(true, false).unwrap();
    let mut record = empty_record();

    match populate_record_at_offset_from_end(&mut config_file, &mut record, 0) {
    	Ok(_) => {},
    	Err(e) => {
    		println!("Couldn't read entry: {}.\nExiting.", e);
			process::exit(1)
    	}
    }

    if record.action == Action::PunchIn {
    	let current_timestamp = chrono::UTC::now();
    	let time_punched_in = current_timestamp.sub(record.timestamp);
    	println!("Punched in since {} ({})", record.timestamp, format_duration(time_punched_in))
    }
    else {
    	let mut previous_record = empty_record();
    	match populate_record_at_offset_from_end(&mut config_file, &mut previous_record, 1) {
	    	Ok(_) => {},
	    	Err(e) => {
	    		println!("Couldn't read entry: {}.\nExiting.", e);
				process::exit(1)
	    	}
	    }

    	let delta = record.timestamp.sub(previous_record.timestamp);
    	println!("Previously punched in between {} and {} ({})",
    		previous_record.timestamp, record.timestamp, format_duration(delta))
    }
}

fn format_duration(duration: chrono::Duration) -> String {
	format!("{:02}h{:02}m", duration.num_hours(), duration.num_minutes())
}

fn get_last_record_action() -> Action {
	let mut config_file = get_conf_file(true, false).unwrap();
    let mut record = empty_record();

    match populate_record_at_offset_from_end(&mut config_file, &mut record, 0) {
    	Ok(_) => {},
    	Err(e) => {
    		println!("Couldn't create punch log: {}.\nExiting.", e);
			process::exit(1)
    	}
    }

	record.action
}

fn ensure_last_record_is_of_action(expected_action: Action) {

    let last_action = get_last_record_action();

    if last_action != expected_action {
    	match expected_action {
    		Action::PunchIn => {
    			println!("Already punched out, punch in first!");
				process::exit(0)
    		}
    		Action::PunchOut => {
    			println!("Already punched in, punch out first!");
    			process::exit(0)
    		}
    		Action::Unset => {
    			println!("Found unset action, log file corrupt");
    			process::exit(1)
    		}
    	}
    }
}

fn get_conf_file(read: bool, append: bool) -> io::Result<File> {
	let mut conf_file = PathBuf::new();
    conf_file.push(env::home_dir().unwrap());
    conf_file.push(".punch");
    conf_file.push("punch.log");

    OpenOptions::new().read(read).append(append).open(conf_file)
}

fn empty_record() -> Record {
	Record {
    	action: Action::Unset,
    	timestamp: chrono::UTC::now()
    }
}

fn populate_record_at_offset_from_end(config_file: &mut File, record: &mut Record, offset_from_end: u64) -> Result<(), String> {
	return seek_to_record_offset(config_file, offset_from_end).
		and_then(|_| populate_record_at_current_offset(config_file, record))
}

fn populate_record_at_current_offset(f: &mut File, record: &mut Record) -> Result<(), String> {
	let mut data = [0 as u8; RECORD_LENGTH];
	let read = f.read(&mut data);
	if read.unwrap() != RECORD_LENGTH {
		panic!("Could not read complete record of {} bytes", RECORD_LENGTH)
	}
	let (ts_data, rest) = data.split_at(19);
	let timestamp = str::from_utf8(&ts_data).unwrap();
	let parse_result = chrono::UTC.datetime_from_str(&timestamp, "%FT%T");

	let record_ts = parse_result.unwrap().with_timezone(&chrono::UTC);
	record.timestamp = record_ts;
	let action_string = str::from_utf8(&rest).unwrap();
	if action_string == "_O\n" {
		record.action = Action::PunchOut;
	}
	else if action_string == "_I\n" {
		record.action = Action::PunchIn;
	}
	else {
		return Err(format!("Could not determine action type from '{}'", action_string))
	}
	Ok(())
}

fn seek_to_record_offset(f: &mut File, record_offset: u64) -> Result<(), String> {
	let m = f.metadata().unwrap();
	let file_len = m.len();

	if file_len < RECORD_LENGTH as u64 {
		return Err(String::from("No data in log - punch in first!"))
	}

	let record_length_in_bytes = RECORD_LENGTH as u64;
	let seek_offset = file_len - ((record_offset + 1) * record_length_in_bytes);

	if f.seek(SeekFrom::Start(seek_offset)).unwrap() != seek_offset {
		return Err(format!("Could not seek to record offset {}", seek_offset))
	}
	Ok(())
}

fn append_to_file(data: &[u8], f: &mut File) {
	match f.write_all(data) {
    	Ok(_) => {},
    	Err(e) => println!("Failed to write data to log: {}", e)
    }
}

fn ensure_log_file_exists() -> io::Result<()> {
    let mut conf_dir = PathBuf::new();
    conf_dir.push(env::home_dir().unwrap());
    conf_dir.push(".punch");
    let config_path = conf_dir.as_path();

    let mut conf_file_builder = PathBuf::from(config_path);
    conf_file_builder.push("punch.log");

    let mut dir_builder = DirBuilder::new();
    dir_builder.recursive(true);

    try!(dir_builder.create(config_path));

    let conf_file = conf_file_builder.as_path();
    match OpenOptions::new().create(true).write(true).open(conf_file) {
    	Ok(_) => Ok(()),
    	Err(e) => Err(e)
    }
}<|endoftext|>