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

pub use errors;

use std::process::Command;
use utils::say::{say, sayln, Spinner};
use errors::{DeliveryError, Kind};
use std::env;
use std::path::{Path, PathBuf};
use std::convert::AsRef;
use std::error;
use std::fs::PathExt;

fn cwd() -> PathBuf {
    env::current_dir().unwrap()
}

pub fn get_head() -> Result<String, DeliveryError> {
    let gitr = try!(git_command(&["branch"], &cwd()));
    let result = try!(parse_get_head(&gitr.stdout));
    Ok(result)
}

fn parse_get_head(stdout: &str) -> Result<String, DeliveryError> {
    for line in stdout.lines_any() {
        let r = regex!(r"(.) (.+)");
        let caps_result = r.captures(line);
        let caps = match caps_result {
            Some(caps) => caps,
            None => { return Err(DeliveryError{ kind: Kind::BadGitOutputMatch, detail: Some(format!("Failed to match: {}", line)) }) }
        };
        let token = caps.at(1).unwrap();
        if token == "*" {
            let branch = caps.at(2).unwrap();
            return Ok(String::from_str(branch));
        }
    }
    return Err(DeliveryError{ kind: Kind::NotOnABranch, detail: None });
}

#[test]
fn test_parse_get_head() {
    let stdout = "  adam/review
  adam/test
  adam/test6
  builder
  first
  foo
  foo2
* master
  snazzy
  testerton";
    let result = parse_get_head(stdout);
    match result {
        Ok(branch) => {
            assert_eq!(&branch[..], "master");
        },
        Err(_) => panic!("No result")
    };
}

pub struct GitResult {
    pub stdout: String,
    pub stderr: String
}

// What is this crazy type signature, you ask? Let me explain!
//
// Where <P: ?Sized> == Any Type (Sized or Unsized)
// Where P: AsRef<Path> == Any type that implements the AsRef<Path> trait
pub fn git_command<P: ?Sized>(args: &[&str], c: &P) -> Result<GitResult, DeliveryError> where P: AsRef<Path> {
    let cwd = c.as_ref();
    let spinner = Spinner::start();
    let mut command = Command::new("git");
    command.args(args);
    command.current_dir(cwd);
    debug!("Git command: {:?}", command);
    let output = match command.output() {
        Ok(o) => o,
        Err(e) => { spinner.stop(); return Err(DeliveryError{ kind: Kind::FailedToExecute, detail: Some(format!("failed to execute git: {}", error::Error::description(&e)))}) },
    };
    debug!("Git exited: {}", output.status);
    spinner.stop();
    if !output.status.success() {
        return Err(DeliveryError{ kind: Kind::GitFailed, detail: Some(format!("STDOUT: {}\nSTDERR: {}\n", String::from_utf8_lossy(&output.stdout), String::from_utf8_lossy(&output.stderr)))});
    }
    let stdout = String::from_utf8_lossy(&output.stdout).to_string();
    debug!("Git stdout: {}", stdout);
    let stderr = String::from_utf8_lossy(&output.stderr).to_string();
    debug!("Git stderr: {}", stderr);
    Ok(GitResult{ stdout: stdout, stderr: stderr })
}

pub fn git_push_review(branch: &str,
                       target: &str) -> Result<ReviewResult, DeliveryError> {
    let gitr = try!(git_command(&["push",
                                  "--porcelain", "--progress",
                                  "--verbose", "delivery",
                                  &format!("{}:_for/{}/{}",
                                           branch, target, branch)],
                                &cwd()));
    parse_git_push_output(&gitr.stdout, &gitr.stderr)
}

/// Output via `sayln` results of a git push.
pub fn say_push_results(results: Vec<PushResult>) {
    for result in results.iter() {
        match result.flag {
            PushResultFlag::SuccessfulFastForward =>
                sayln("green", &format!("Updated change: {}", result.reason)),
            PushResultFlag::SuccessfulForcedUpdate =>
                sayln("green",
                      &format!("Force updated change: {}", result.reason)),
            PushResultFlag::SuccessfulDeletedRef =>
                sayln("red", &format!("Deleted change: {}", result.reason)),
            PushResultFlag::SuccessfulPushedNewRef =>
                sayln("green", &format!("Created change: {}", result.reason)),
            PushResultFlag::Rejected =>
                sayln("red", &format!("Rejected change: {}", result.reason)),
            PushResultFlag::UpToDate =>
                sayln("yellow",
                      &format!("Nothing added to the existing change")),
        }
    }
}

#[derive(Debug, Clone)]
pub enum PushResultFlag {
    SuccessfulFastForward,
    SuccessfulForcedUpdate,
    SuccessfulDeletedRef,
    SuccessfulPushedNewRef,
    Rejected,
    UpToDate,
}

impl Copy for PushResultFlag { }

/// Returned by `git_push_review`. The `push_results` field is a
/// vector of `PushResult` each indicating a `PushResultFalg` and a
/// reason message. The `messages` field is a vector of output lines
/// returned from the server managing the git protocol (as you'd see
/// on the command line prefixed with `remote: $LINE`. The `url` field
/// will contain the last line that looks like a URL returned as
/// remote data.
#[derive(Debug)]
pub struct ReviewResult {
    pub push_results: Vec<PushResult>,
    pub messages: Vec<String>,
    pub url: Option<String>,
    pub change_id: Option<String>
}

#[derive(Debug)]
pub struct PushResult {
    flag: PushResultFlag,
    reason: String
}

pub fn parse_git_push_output(push_output: &str,
                             push_error: &str) -> Result<ReviewResult, DeliveryError> {
    let mut push_results: Vec<PushResult> = Vec::new();
    let mut r_messages: Vec<String> = Vec::new();
    let mut r_url = None;
    let mut r_change_id = None;
    for line in push_error.lines_any() {
        debug!("error: {}", line);
        if line.starts_with("remote") {
            let r = regex!(r"remote: ([^ ]+)");
            let caps_result = r.captures(line);
            match caps_result {
                Some(caps) => {
                    let cap = caps.at(1).unwrap();
                    if cap.starts_with("http") {
                        let change_url = cap.trim().to_string();
                        r_url = Some(change_url.clone());
                        let change_id_regex = regex!(r"/([a-f0-9]{8}-(?:[a-f0-9]{4}-){3}[a-f0-9]{12})");
                        let change_id_match = change_id_regex.captures(change_url.as_str());
                        r_change_id = Some(String::from_str(change_id_match.unwrap().at(1).unwrap()));
                    } else {
                        r_messages.push(cap.to_string());
                    }
                 },
                None => {}
            }
        }
    }
    for line in push_output.lines_any() {
        debug!("output: {}", line);
        if line.starts_with("To") {
            continue;
        } else if line.starts_with("Done") {
            continue;
        }
        let r = regex!(r"(.)\t(.+):(.+)\t\[(.+)\]");
        let caps_result = r.captures(line);
        let caps = match caps_result {
            Some(caps) => caps,
            None => {
                let detail = Some(format!("Failed to match: {}", line));
                return Err(DeliveryError{ kind: Kind::BadGitOutputMatch,
                                          detail: detail })
            }
        };
        let result_flag = match caps.at(1).unwrap() {
            " " => PushResultFlag::SuccessfulFastForward,
            "+" => PushResultFlag::SuccessfulForcedUpdate,
            "-" => PushResultFlag::SuccessfulDeletedRef,
            "*" => PushResultFlag::SuccessfulPushedNewRef,
            "!" => PushResultFlag::Rejected,
            "=" => PushResultFlag::UpToDate,
            _ => {
                return Err(DeliveryError{
                    kind: Kind::BadGitOutputMatch,
                    detail: Some(format!("Unknown result flag"))})
            }
        };
        push_results.push(
            PushResult{
                flag: result_flag,
                reason: String::from_str(caps.at(4).unwrap())
            }
            )
    }
    Ok(ReviewResult { push_results: push_results,
                      messages: r_messages,
                      url: r_url,
                      change_id: r_change_id })
}

pub fn delivery_ssh_url(user: &str, server: &str, ent: &str, org: &str, proj: &str) -> String {
    format!("ssh://{}@{}@{}:8989/{}/{}/{}", user, ent, server, ent, org, proj)
}

pub fn init_repo(path: &PathBuf) -> Result<(), DeliveryError> {
    say("white", "Is ");
    say("magenta", &format!("{} ", path.display()));
    say("white", "a git repo?  ");

    let git_dir = Path::new("./.git");

    if git_dir.exists() {
        sayln("white", "yes");
        return Ok(())
    } else {
        sayln("red", "no. Run 'git init' here and then 'delivery init' again.");
        return Err(DeliveryError{ kind: Kind::GitSetupFailed, detail: None })
    }
}

// This function is not currently used, but will be when we
// add a --force option to the init command.
pub fn create_repo(path: &PathBuf) -> Result<(), DeliveryError> {
    say("white", "Creating repo in: ");
    say("magenta", &format!("{} ", path.display()));
    let result = git_command(&["init"], path);
    match result {
        Ok(_) => {
            sayln("white", "'git init' done.");
            return Ok(());
        },
        Err(e) => return Err(e)
    }
}

pub fn config_repo(user: &str, server: &str, ent: &str, org: &str, proj: &str, path: &PathBuf) -> Result<bool, DeliveryError> {
    sayln("white", &format!("adding remote: {}", &delivery_ssh_url(user, server, ent, org, proj)));
    let url = delivery_ssh_url(user, server, ent, org, proj);
    let result = git_command(&["remote", "add", "delivery", &url], path);
    match result {
        Ok(_) => return Ok(true),
        Err(e) => {
            match e.detail.clone() {
                Some(msg) => {
                    if msg.contains("remote delivery already exists") {
                        return Ok(false);
                    } else {
                        return Err(e)
                    }
                },
                None => {
                    return Err(e)
                }
            }
        },
    }
}

pub fn checkout_branch_name(change: &str, patchset: &str) -> String {
    if patchset == "latest" {
        return String::from_str(change);
    } else {
        return format!("{}/{}", change, patchset);
    }
}

pub fn diff(change: &str, patchset: &str, pipeline: &str, local: &bool) -> Result<(), DeliveryError> {
    try!(git_command(&["fetch", "delivery"], &cwd()));
    let mut first_branch = format!("delivery/{}", pipeline);
    if *local {
        first_branch = String::from_str("HEAD");
    }
    let diff = try!(git_command(&["diff", "--color=always", &first_branch, &format!("delivery/_reviews/{}/{}/{}", pipeline, change, patchset)], &cwd()));
    say("white", "\n");
    sayln("white", &diff.stdout);
    Ok(())
}

pub fn clone(project: &str, git_url: &str) -> Result<(), DeliveryError> {
    try!(git_command(&["clone", git_url, project], &cwd()));
    Ok(())
}

pub fn checkout_review(change: &str, patchset: &str, pipeline: &str) -> Result<(), DeliveryError> {
    try!(git_command(&["fetch", "delivery"], &cwd()));
    let branchname = checkout_branch_name(change, patchset);
    let result = git_command(&["branch", "--track", &branchname, &format!("delivery/_reviews/{}/{}/{}", pipeline, change, patchset)], &cwd());
    match result {
        Ok(_) => {
            try!(git_command(&["checkout", &branchname], &cwd()));
            return Ok(())
        },
        Err(e) => {
            match e.detail {
                Some(msg) => {
                    if msg.contains("already exists.") {
                        try!(git_command(&["checkout", &branchname], &cwd()));
                        sayln("white", "Branch already exists, checking it out.");
                        let r = try!(git_command(&["status"], &cwd()));
                        sayln("white", &r.stdout);
                        return Ok(())
                    } else {
                        return Err(DeliveryError{kind: Kind::GitFailed, detail: Some(msg)});
                    }
                },
                None => {
                    return Err(e)
                }
            }
        },
    }
}

pub fn server_content() -> bool {
    match git_command(&["ls-remote", "delivery", "refs/heads/master"], &cwd()) {
        Ok(msg) => {
            if msg.stdout.contains("refs/heads/master") {
                say("red", &format!("{}", msg.stdout));
                return true
            } else {
                sayln("white", "No upstream content");
                return false
            }
        },
        Err(e) => {
            sayln("red", &format!("got error {:?}", e));
            return false
        }
    }
}

pub fn git_push_master() -> Result<(), DeliveryError> {
    match git_command(&["push", "--set-upstream",
                       "--porcelain", "--progress",
                       "--verbose", "delivery", "master"],
                      &cwd()) {
        Ok(msg) => {
            sayln("white", &format!("{}", msg.stdout));
            return Ok(())
        },
        Err(e) => {
            match e.detail {
                Some(msg) => {
                    if msg.contains("failed to push some refs") {
                        sayln("red", &format!("Failed to push; perhaps there are no local commits?"));
                    }
                    return Ok(())
                },
                None => {
                    return Err(e)
                }
            }
        }
    }
}
<commit_msg>Fixing a bug with `delivery review` opening percent-escaped URLs on some systems<commit_after>//
// Copyright:: Copyright (c) 2015 Chef Software, Inc.
// License:: Apache License, Version 2.0
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

pub use errors;

use std::process::Command;
use utils::say::{say, sayln, Spinner};
use errors::{DeliveryError, Kind};
use std::env;
use std::path::{Path, PathBuf};
use std::convert::AsRef;
use std::error;
use std::fs::PathExt;

fn cwd() -> PathBuf {
    env::current_dir().unwrap()
}

pub fn get_head() -> Result<String, DeliveryError> {
    let gitr = try!(git_command(&["branch"], &cwd()));
    let result = try!(parse_get_head(&gitr.stdout));
    Ok(result)
}

fn parse_get_head(stdout: &str) -> Result<String, DeliveryError> {
    for line in stdout.lines_any() {
        let r = regex!(r"(.) (.+)");
        let caps_result = r.captures(line);
        let caps = match caps_result {
            Some(caps) => caps,
            None => { return Err(DeliveryError{ kind: Kind::BadGitOutputMatch, detail: Some(format!("Failed to match: {}", line)) }) }
        };
        let token = caps.at(1).unwrap();
        if token == "*" {
            let branch = caps.at(2).unwrap();
            return Ok(String::from_str(branch));
        }
    }
    return Err(DeliveryError{ kind: Kind::NotOnABranch, detail: None });
}

#[test]
fn test_parse_get_head() {
    let stdout = "  adam/review
  adam/test
  adam/test6
  builder
  first
  foo
  foo2
* master
  snazzy
  testerton";
    let result = parse_get_head(stdout);
    match result {
        Ok(branch) => {
            assert_eq!(&branch[..], "master");
        },
        Err(_) => panic!("No result")
    };
}

pub struct GitResult {
    pub stdout: String,
    pub stderr: String
}

// What is this crazy type signature, you ask? Let me explain!
//
// Where <P: ?Sized> == Any Type (Sized or Unsized)
// Where P: AsRef<Path> == Any type that implements the AsRef<Path> trait
pub fn git_command<P: ?Sized>(args: &[&str], c: &P) -> Result<GitResult, DeliveryError> where P: AsRef<Path> {
    let cwd = c.as_ref();
    let spinner = Spinner::start();
    let mut command = Command::new("git");
    command.args(args);
    command.current_dir(cwd);
    debug!("Git command: {:?}", command);
    let output = match command.output() {
        Ok(o) => o,
        Err(e) => { spinner.stop(); return Err(DeliveryError{ kind: Kind::FailedToExecute, detail: Some(format!("failed to execute git: {}", error::Error::description(&e)))}) },
    };
    debug!("Git exited: {}", output.status);
    spinner.stop();
    if !output.status.success() {
        return Err(DeliveryError{ kind: Kind::GitFailed, detail: Some(format!("STDOUT: {}\nSTDERR: {}\n", String::from_utf8_lossy(&output.stdout), String::from_utf8_lossy(&output.stderr)))});
    }
    let stdout = String::from_utf8_lossy(&output.stdout).to_string();
    debug!("Git stdout: {}", stdout);
    let stderr = String::from_utf8_lossy(&output.stderr).to_string();
    debug!("Git stderr: {}", stderr);
    Ok(GitResult{ stdout: stdout, stderr: stderr })
}

pub fn git_push_review(branch: &str,
                       target: &str) -> Result<ReviewResult, DeliveryError> {
    let gitr = try!(git_command(&["push",
                                  "--porcelain", "--progress",
                                  "--verbose", "delivery",
                                  &format!("{}:_for/{}/{}",
                                           branch, target, branch)],
                                &cwd()));
    parse_git_push_output(&gitr.stdout, &gitr.stderr)
}

/// Output via `sayln` results of a git push.
pub fn say_push_results(results: Vec<PushResult>) {
    for result in results.iter() {
        match result.flag {
            PushResultFlag::SuccessfulFastForward =>
                sayln("green", &format!("Updated change: {}", result.reason)),
            PushResultFlag::SuccessfulForcedUpdate =>
                sayln("green",
                      &format!("Force updated change: {}", result.reason)),
            PushResultFlag::SuccessfulDeletedRef =>
                sayln("red", &format!("Deleted change: {}", result.reason)),
            PushResultFlag::SuccessfulPushedNewRef =>
                sayln("green", &format!("Created change: {}", result.reason)),
            PushResultFlag::Rejected =>
                sayln("red", &format!("Rejected change: {}", result.reason)),
            PushResultFlag::UpToDate =>
                sayln("yellow",
                      &format!("Nothing added to the existing change")),
        }
    }
}

#[derive(Debug, Clone)]
pub enum PushResultFlag {
    SuccessfulFastForward,
    SuccessfulForcedUpdate,
    SuccessfulDeletedRef,
    SuccessfulPushedNewRef,
    Rejected,
    UpToDate,
}

impl Copy for PushResultFlag { }

/// Returned by `git_push_review`. The `push_results` field is a
/// vector of `PushResult` each indicating a `PushResultFalg` and a
/// reason message. The `messages` field is a vector of output lines
/// returned from the server managing the git protocol (as you'd see
/// on the command line prefixed with `remote: $LINE`. The `url` field
/// will contain the last line that looks like a URL returned as
/// remote data.
#[derive(Debug)]
pub struct ReviewResult {
    pub push_results: Vec<PushResult>,
    pub messages: Vec<String>,
    pub url: Option<String>,
    pub change_id: Option<String>
}

#[derive(Debug)]
pub struct PushResult {
    flag: PushResultFlag,
    reason: String
}

pub fn parse_git_push_output(push_output: &str,
                             push_error: &str) -> Result<ReviewResult, DeliveryError> {
    let mut push_results: Vec<PushResult> = Vec::new();
    let mut r_messages: Vec<String> = Vec::new();
    let mut r_url = None;
    let mut r_change_id = None;
    for line in push_error.lines_any() {
        debug!("error: {}", line);
        if line.starts_with("remote") {
            // this weird regex accounts for the fact that some versions of git
            // (at least 1.8.5.2 (Apple Git-48), but possibly others) append the
            // ANSI code ESC[K to every line of the remote's answer when pushing
            let r = regex!(r"remote: ([^ \x{1b}]+)(?:\x{1b}\[K)?$");
            let caps_result = r.captures(line);
            match caps_result {
                Some(caps) => {
                    let cap = caps.at(1).unwrap();
                    if cap.starts_with("http") {
                        let change_url = cap.trim().to_string();
                        r_url = Some(change_url.clone());
                        let change_id_regex = regex!(r"/([a-f0-9]{8}-(?:[a-f0-9]{4}-){3}[a-f0-9]{12})");
                        let change_id_match = change_id_regex.captures(change_url.as_str());
                        r_change_id = Some(String::from_str(change_id_match.unwrap().at(1).unwrap()));
                    } else {
                        r_messages.push(cap.to_string());
                    }
                 },
                None => {}
            }
        }
    }
    for line in push_output.lines_any() {
        debug!("output: {}", line);
        if line.starts_with("To") {
            continue;
        } else if line.starts_with("Done") {
            continue;
        }
        let r = regex!(r"(.)\t(.+):(.+)\t\[(.+)\]");
        let caps_result = r.captures(line);
        let caps = match caps_result {
            Some(caps) => caps,
            None => {
                let detail = Some(format!("Failed to match: {}", line));
                return Err(DeliveryError{ kind: Kind::BadGitOutputMatch,
                                          detail: detail })
            }
        };
        let result_flag = match caps.at(1).unwrap() {
            " " => PushResultFlag::SuccessfulFastForward,
            "+" => PushResultFlag::SuccessfulForcedUpdate,
            "-" => PushResultFlag::SuccessfulDeletedRef,
            "*" => PushResultFlag::SuccessfulPushedNewRef,
            "!" => PushResultFlag::Rejected,
            "=" => PushResultFlag::UpToDate,
            _ => {
                return Err(DeliveryError{
                    kind: Kind::BadGitOutputMatch,
                    detail: Some(format!("Unknown result flag"))})
            }
        };
        push_results.push(
            PushResult{
                flag: result_flag,
                reason: String::from_str(caps.at(4).unwrap())
            }
            )
    }
    Ok(ReviewResult { push_results: push_results,
                      messages: r_messages,
                      url: r_url,
                      change_id: r_change_id })
}

pub fn delivery_ssh_url(user: &str, server: &str, ent: &str, org: &str, proj: &str) -> String {
    format!("ssh://{}@{}@{}:8989/{}/{}/{}", user, ent, server, ent, org, proj)
}

pub fn init_repo(path: &PathBuf) -> Result<(), DeliveryError> {
    say("white", "Is ");
    say("magenta", &format!("{} ", path.display()));
    say("white", "a git repo?  ");

    let git_dir = Path::new("./.git");

    if git_dir.exists() {
        sayln("white", "yes");
        return Ok(())
    } else {
        sayln("red", "no. Run 'git init' here and then 'delivery init' again.");
        return Err(DeliveryError{ kind: Kind::GitSetupFailed, detail: None })
    }
}

// This function is not currently used, but will be when we
// add a --force option to the init command.
pub fn create_repo(path: &PathBuf) -> Result<(), DeliveryError> {
    say("white", "Creating repo in: ");
    say("magenta", &format!("{} ", path.display()));
    let result = git_command(&["init"], path);
    match result {
        Ok(_) => {
            sayln("white", "'git init' done.");
            return Ok(());
        },
        Err(e) => return Err(e)
    }
}

pub fn config_repo(user: &str, server: &str, ent: &str, org: &str, proj: &str, path: &PathBuf) -> Result<bool, DeliveryError> {
    sayln("white", &format!("adding remote: {}", &delivery_ssh_url(user, server, ent, org, proj)));
    let url = delivery_ssh_url(user, server, ent, org, proj);
    let result = git_command(&["remote", "add", "delivery", &url], path);
    match result {
        Ok(_) => return Ok(true),
        Err(e) => {
            match e.detail.clone() {
                Some(msg) => {
                    if msg.contains("remote delivery already exists") {
                        return Ok(false);
                    } else {
                        return Err(e)
                    }
                },
                None => {
                    return Err(e)
                }
            }
        },
    }
}

pub fn checkout_branch_name(change: &str, patchset: &str) -> String {
    if patchset == "latest" {
        return String::from_str(change);
    } else {
        return format!("{}/{}", change, patchset);
    }
}

pub fn diff(change: &str, patchset: &str, pipeline: &str, local: &bool) -> Result<(), DeliveryError> {
    try!(git_command(&["fetch", "delivery"], &cwd()));
    let mut first_branch = format!("delivery/{}", pipeline);
    if *local {
        first_branch = String::from_str("HEAD");
    }
    let diff = try!(git_command(&["diff", "--color=always", &first_branch, &format!("delivery/_reviews/{}/{}/{}", pipeline, change, patchset)], &cwd()));
    say("white", "\n");
    sayln("white", &diff.stdout);
    Ok(())
}

pub fn clone(project: &str, git_url: &str) -> Result<(), DeliveryError> {
    try!(git_command(&["clone", git_url, project], &cwd()));
    Ok(())
}

pub fn checkout_review(change: &str, patchset: &str, pipeline: &str) -> Result<(), DeliveryError> {
    try!(git_command(&["fetch", "delivery"], &cwd()));
    let branchname = checkout_branch_name(change, patchset);
    let result = git_command(&["branch", "--track", &branchname, &format!("delivery/_reviews/{}/{}/{}", pipeline, change, patchset)], &cwd());
    match result {
        Ok(_) => {
            try!(git_command(&["checkout", &branchname], &cwd()));
            return Ok(())
        },
        Err(e) => {
            match e.detail {
                Some(msg) => {
                    if msg.contains("already exists.") {
                        try!(git_command(&["checkout", &branchname], &cwd()));
                        sayln("white", "Branch already exists, checking it out.");
                        let r = try!(git_command(&["status"], &cwd()));
                        sayln("white", &r.stdout);
                        return Ok(())
                    } else {
                        return Err(DeliveryError{kind: Kind::GitFailed, detail: Some(msg)});
                    }
                },
                None => {
                    return Err(e)
                }
            }
        },
    }
}

pub fn server_content() -> bool {
    match git_command(&["ls-remote", "delivery", "refs/heads/master"], &cwd()) {
        Ok(msg) => {
            if msg.stdout.contains("refs/heads/master") {
                say("red", &format!("{}", msg.stdout));
                return true
            } else {
                sayln("white", "No upstream content");
                return false
            }
        },
        Err(e) => {
            sayln("red", &format!("got error {:?}", e));
            return false
        }
    }
}

pub fn git_push_master() -> Result<(), DeliveryError> {
    match git_command(&["push", "--set-upstream",
                       "--porcelain", "--progress",
                       "--verbose", "delivery", "master"],
                      &cwd()) {
        Ok(msg) => {
            sayln("white", &format!("{}", msg.stdout));
            return Ok(())
        },
        Err(e) => {
            match e.detail {
                Some(msg) => {
                    if msg.contains("failed to push some refs") {
                        sayln("red", &format!("Failed to push; perhaps there are no local commits?"));
                    }
                    return Ok(())
                },
                None => {
                    return Err(e)
                }
            }
        }
    }
}
<|endoftext|>


<commit_before>use crate::directory::read_only_source::ReadOnlySource;
use crate::directory::{AntiCallToken, TerminatingWrite};
use byteorder::{ByteOrder, LittleEndian};
use crc32fast::Hasher;
use std::io;
use std::io::Write;

const COMMON_FOOTER_SIZE: usize = 4 * 5;

#[derive(Debug, Clone, PartialEq)]
pub struct Footer {
    pub tantivy_version: (u32, u32, u32),
    pub meta: String,
    pub versioned_footer: VersionedFooter,
}

impl Footer {
    pub fn new(versioned_footer: VersionedFooter) -> Self {
        let tantivy_version = (
            env!("CARGO_PKG_VERSION_MAJOR").parse().unwrap(),
            env!("CARGO_PKG_VERSION_MINOR").parse().unwrap(),
            env!("CARGO_PKG_VERSION_PATCH").parse().unwrap(),
        );
        Footer {
            tantivy_version,
            meta: format!(
                "tantivy {}.{}.{}, index v{}",
                tantivy_version.0,
                tantivy_version.1,
                tantivy_version.2,
                versioned_footer.version()
            ),
            versioned_footer,
        }
    }

    pub fn to_bytes(&self) -> Vec<u8> {
        let mut res = self.versioned_footer.to_bytes();
        res.extend_from_slice(self.meta.as_bytes());
        let len = res.len();
        res.resize(len + COMMON_FOOTER_SIZE, 0);
        let mut common_footer = &mut res[len..];
        LittleEndian::write_u32(&mut common_footer, self.meta.len() as u32);
        LittleEndian::write_u32(&mut common_footer[4..], self.tantivy_version.0);
        LittleEndian::write_u32(&mut common_footer[8..], self.tantivy_version.1);
        LittleEndian::write_u32(&mut common_footer[12..], self.tantivy_version.2);
        LittleEndian::write_u32(&mut common_footer[16..], (len + COMMON_FOOTER_SIZE) as u32);
        res
    }

    pub fn from_bytes(data: &[u8]) -> Result<Self, io::Error> {
        let len = data.len();
        if len < COMMON_FOOTER_SIZE + 4 {
            // 4 bytes for index version, stored in versioned footer
            return Err(io::Error::new(
                    io::ErrorKind::UnexpectedEof,
                    format!("File corrupted. The footer len must be over 24, while the entire file len is {}", len)
                    )
            );
        }

        let size = LittleEndian::read_u32(&data[len - 4..]) as usize;
        if len < size as usize {
            return Err(io::Error::new(
                io::ErrorKind::UnexpectedEof,
                format!(
                    "File corrupted. The footer len is {}, while the entire file len is {}",
                    size, len
                ),
            ));
        }
        let footer = &data[len - size as usize..];
        let meta_len = LittleEndian::read_u32(&footer[size - 20..]) as usize;
        let tantivy_major = LittleEndian::read_u32(&footer[size - 16..]);
        let tantivy_minor = LittleEndian::read_u32(&footer[size - 12..]);
        let tantivy_patch = LittleEndian::read_u32(&footer[size - 8..]);
        Ok(Footer {
            tantivy_version: (tantivy_major, tantivy_minor, tantivy_patch),
            meta: String::from_utf8_lossy(&footer[size - meta_len - 20..size - 20]).into_owned(),
            versioned_footer: VersionedFooter::from_bytes(&footer[..size - meta_len - 20])?,
        })
    }

    pub fn extract_footer(source: ReadOnlySource) -> Result<(Footer, ReadOnlySource), io::Error> {
        let footer = Footer::from_bytes(source.as_slice())?;
        let reader = source.slice_to(source.as_slice().len() - footer.size());
        Ok((footer, reader))
    }

    pub fn size(&self) -> usize {
        self.versioned_footer.size() as usize + self.meta.len() + 20
    }
}

#[derive(Debug, Clone, PartialEq)]
pub enum VersionedFooter {
    UnknownVersion { version: u32, size: u32 },
    V0(u32), // crc
}

impl VersionedFooter {
    pub fn to_bytes(&self) -> Vec<u8> {
        match self {
            Self::V0(crc) => {
                let mut res = vec![0; 8];
                LittleEndian::write_u32(&mut res, 0);
                LittleEndian::write_u32(&mut res[4..], *crc);
                res
            }
            Self::UnknownVersion { .. } => {
                panic!("Unsupported index should never get serialized");
            }
        }
    }

    pub fn from_bytes(footer: &[u8]) -> Result<Self, io::Error> {
        assert!(footer.len() >= 4);
        let version = LittleEndian::read_u32(footer);
        match version {
            0 => {
                if footer.len() == 8 {
                    Ok(Self::V0(LittleEndian::read_u32(&footer[4..])))
                } else {
                    Err(io::Error::new(
                        io::ErrorKind::UnexpectedEof,
                        format!(
                            "File corrupted. The versioned footer len is {}, while it should be 8",
                            footer.len()
                        ),
                    ))
                }
            }
            version => Ok(Self::UnknownVersion {
                version,
                size: footer.len() as u32,
            }),
        }
    }

    pub fn size(&self) -> u32 {
        match self {
            Self::V0(_) => 8,
            Self::UnknownVersion { size, .. } => *size,
        }
    }

    pub fn version(&self) -> u32 {
        match self {
            Self::V0(_) => 0,
            Self::UnknownVersion { version, .. } => *version,
        }
    }

    pub fn crc(&self) -> Option<u32> {
        match self {
            Self::V0(crc) => Some(*crc),
            Self::UnknownVersion { .. } => None,
        }
    }
}

pub(crate) struct FooterProxy<W: TerminatingWrite> {
    /// always Some except after terminate call
    hasher: Option<Hasher>,
    /// always Some except after terminate call
    writer: Option<W>,
}

impl<W: TerminatingWrite> FooterProxy<W> {
    pub fn new(writer: W) -> Self {
        FooterProxy {
            hasher: Some(Hasher::new()),
            writer: Some(writer),
        }
    }
}

impl<W: TerminatingWrite> Write for FooterProxy<W> {
    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
        let count = self.writer.as_mut().unwrap().write(buf)?;
        self.hasher.as_mut().unwrap().update(&buf[..count]);
        Ok(count)
    }

    fn flush(&mut self) -> io::Result<()> {
        self.writer.as_mut().unwrap().flush()
    }
}

impl<W: TerminatingWrite> TerminatingWrite for FooterProxy<W> {
    fn terminate_ref(&mut self, _: AntiCallToken) -> io::Result<()> {
        let crc = self.hasher.take().unwrap().finalize();

        let footer = Footer::new(VersionedFooter::V0(crc)).to_bytes();
        let mut writer = self.writer.take().unwrap();
        writer.write_all(&footer)?;
        writer.terminate()
    }
}

#[cfg(test)]
mod tests {
    use crate::directory::footer::{Footer, VersionedFooter};

    #[test]
    fn test_serialize_deserialize_footer() {
        let crc = 123456;
        let footer = Footer::new(VersionedFooter::V0(crc));
        let footer_bytes = footer.to_bytes();

        assert_eq!(Footer::from_bytes(&footer_bytes).unwrap(), footer);
    }
}
<commit_msg>Removed enum variants on type alias<commit_after>use crate::directory::read_only_source::ReadOnlySource;
use crate::directory::{AntiCallToken, TerminatingWrite};
use byteorder::{ByteOrder, LittleEndian};
use crc32fast::Hasher;
use std::io;
use std::io::Write;

const COMMON_FOOTER_SIZE: usize = 4 * 5;

#[derive(Debug, Clone, PartialEq)]
pub struct Footer {
    pub tantivy_version: (u32, u32, u32),
    pub meta: String,
    pub versioned_footer: VersionedFooter,
}

impl Footer {
    pub fn new(versioned_footer: VersionedFooter) -> Self {
        let tantivy_version = (
            env!("CARGO_PKG_VERSION_MAJOR").parse().unwrap(),
            env!("CARGO_PKG_VERSION_MINOR").parse().unwrap(),
            env!("CARGO_PKG_VERSION_PATCH").parse().unwrap(),
        );
        Footer {
            tantivy_version,
            meta: format!(
                "tantivy {}.{}.{}, index v{}",
                tantivy_version.0,
                tantivy_version.1,
                tantivy_version.2,
                versioned_footer.version()
            ),
            versioned_footer,
        }
    }

    pub fn to_bytes(&self) -> Vec<u8> {
        let mut res = self.versioned_footer.to_bytes();
        res.extend_from_slice(self.meta.as_bytes());
        let len = res.len();
        res.resize(len + COMMON_FOOTER_SIZE, 0);
        let mut common_footer = &mut res[len..];
        LittleEndian::write_u32(&mut common_footer, self.meta.len() as u32);
        LittleEndian::write_u32(&mut common_footer[4..], self.tantivy_version.0);
        LittleEndian::write_u32(&mut common_footer[8..], self.tantivy_version.1);
        LittleEndian::write_u32(&mut common_footer[12..], self.tantivy_version.2);
        LittleEndian::write_u32(&mut common_footer[16..], (len + COMMON_FOOTER_SIZE) as u32);
        res
    }

    pub fn from_bytes(data: &[u8]) -> Result<Self, io::Error> {
        let len = data.len();
        if len < COMMON_FOOTER_SIZE + 4 {
            // 4 bytes for index version, stored in versioned footer
            return Err(io::Error::new(
                    io::ErrorKind::UnexpectedEof,
                    format!("File corrupted. The footer len must be over 24, while the entire file len is {}", len)
                    )
            );
        }

        let size = LittleEndian::read_u32(&data[len - 4..]) as usize;
        if len < size as usize {
            return Err(io::Error::new(
                io::ErrorKind::UnexpectedEof,
                format!(
                    "File corrupted. The footer len is {}, while the entire file len is {}",
                    size, len
                ),
            ));
        }
        let footer = &data[len - size as usize..];
        let meta_len = LittleEndian::read_u32(&footer[size - 20..]) as usize;
        let tantivy_major = LittleEndian::read_u32(&footer[size - 16..]);
        let tantivy_minor = LittleEndian::read_u32(&footer[size - 12..]);
        let tantivy_patch = LittleEndian::read_u32(&footer[size - 8..]);
        Ok(Footer {
            tantivy_version: (tantivy_major, tantivy_minor, tantivy_patch),
            meta: String::from_utf8_lossy(&footer[size - meta_len - 20..size - 20]).into_owned(),
            versioned_footer: VersionedFooter::from_bytes(&footer[..size - meta_len - 20])?,
        })
    }

    pub fn extract_footer(source: ReadOnlySource) -> Result<(Footer, ReadOnlySource), io::Error> {
        let footer = Footer::from_bytes(source.as_slice())?;
        let reader = source.slice_to(source.as_slice().len() - footer.size());
        Ok((footer, reader))
    }

    pub fn size(&self) -> usize {
        self.versioned_footer.size() as usize + self.meta.len() + 20
    }
}

#[derive(Debug, Clone, PartialEq)]
pub enum VersionedFooter {
    UnknownVersion { version: u32, size: u32 },
    V0(u32), // crc
}

impl VersionedFooter {
    pub fn to_bytes(&self) -> Vec<u8> {
        match self {
            VersionedFooter::V0(crc) => {
                let mut res = vec![0; 8];
                LittleEndian::write_u32(&mut res, 0);
                LittleEndian::write_u32(&mut res[4..], *crc);
                res
            }
            VersionedFooter::UnknownVersion { .. } => {
                panic!("Unsupported index should never get serialized");
            }
        }
    }

    pub fn from_bytes(footer: &[u8]) -> Result<Self, io::Error> {
        assert!(footer.len() >= 4);
        let version = LittleEndian::read_u32(footer);
        match version {
            0 => {
                if footer.len() == 8 {
                    Ok(VersionedFooter::V0(LittleEndian::read_u32(&footer[4..])))
                } else {
                    Err(io::Error::new(
                        io::ErrorKind::UnexpectedEof,
                        format!(
                            "File corrupted. The versioned footer len is {}, while it should be 8",
                            footer.len()
                        ),
                    ))
                }
            }
            version => Ok(VersionedFooter::UnknownVersion {
                version,
                size: footer.len() as u32,
            }),
        }
    }

    pub fn size(&self) -> u32 {
        match self {
            VersionedFooter::V0(_) => 8,
            VersionedFooter::UnknownVersion { size, .. } => *size,
        }
    }

    pub fn version(&self) -> u32 {
        match self {
            VersionedFooter::V0(_) => 0,
            VersionedFooter::UnknownVersion { version, .. } => *version,
        }
    }

    pub fn crc(&self) -> Option<u32> {
        match self {
            VersionedFooter::V0(crc) => Some(*crc),
            VersionedFooter::UnknownVersion { .. } => None,
        }
    }
}

pub(crate) struct FooterProxy<W: TerminatingWrite> {
    /// always Some except after terminate call
    hasher: Option<Hasher>,
    /// always Some except after terminate call
    writer: Option<W>,
}

impl<W: TerminatingWrite> FooterProxy<W> {
    pub fn new(writer: W) -> Self {
        FooterProxy {
            hasher: Some(Hasher::new()),
            writer: Some(writer),
        }
    }
}

impl<W: TerminatingWrite> Write for FooterProxy<W> {
    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
        let count = self.writer.as_mut().unwrap().write(buf)?;
        self.hasher.as_mut().unwrap().update(&buf[..count]);
        Ok(count)
    }

    fn flush(&mut self) -> io::Result<()> {
        self.writer.as_mut().unwrap().flush()
    }
}

impl<W: TerminatingWrite> TerminatingWrite for FooterProxy<W> {
    fn terminate_ref(&mut self, _: AntiCallToken) -> io::Result<()> {
        let crc = self.hasher.take().unwrap().finalize();

        let footer = Footer::new(VersionedFooter::V0(crc)).to_bytes();
        let mut writer = self.writer.take().unwrap();
        writer.write_all(&footer)?;
        writer.terminate()
    }
}

#[cfg(test)]
mod tests {
    use crate::directory::footer::{Footer, VersionedFooter};

    #[test]
    fn test_serialize_deserialize_footer() {
        let crc = 123456;
        let footer = Footer::new(VersionedFooter::V0(crc));
        let footer_bytes = footer.to_bytes();

        assert_eq!(Footer::from_bytes(&footer_bytes).unwrap(), footer);
    }
}
<|endoftext|>


<commit_before>extern crate chrono;

use flowgger::Decoder;
use flowgger::config::Config;
use flowgger::record::{Record, Pri, StructuredData};
use self::chrono::DateTime;

#[derive(Clone)]
pub struct RFC5424;

impl Decoder for RFC5424 {
    fn new(config: &Config) -> RFC5424 {
        let _ = config;
        RFC5424
    }

    fn decode(&self, line: &str) -> Result<Record, &'static str> {
        let (bom, line) = match BOM::parse(line, "<") {
            Ok(bom_line) => bom_line,
            Err(err) => return Err(err)
        };
        let mut parts = line.splitn(7, ' ');
        let pri_version = try!(parse_pri_version(try!(parts.next().ok_or("Missing priority and version"))));
        let ts = try!(parse_ts(try!(parts.next().ok_or("Missing timestamp"))));
        let hostname = try!(parts.next().ok_or("Missing hostname"));
        let appname = try!(parts.next().ok_or("Missing application name"));
        let procid = try!(parts.next().ok_or("Missing process id"));
        let msgid = try!(parts.next().ok_or("Missing message id"));
        let (sd, msg) = try!(parse_data(try!(parts.next().ok_or("Missing message data"))));
        let record = Record {
            pri: Some(pri_version),
            ts: ts,
            hostname: hostname.to_owned(),
            appname: Some(appname.to_owned()),
            procid: Some(procid.to_owned()),
            msgid: Some(msgid.to_owned()),
            sd: sd,
            msg: msg
        };
        Ok(record)
    }
}

impl Pri {
    fn new(encoded: u8) -> Pri {
        Pri {
            facility: encoded >> 3,
            severity: encoded & 7
        }
    }
}

enum BOM {
    NONE,
    UTF8
}

impl BOM {
    fn parse<'a>(line: &'a str, sep: &str) -> Result<(BOM, &'a str), &'static str> {
        if line.starts_with("\u{feff}") {
            Ok((BOM::UTF8, &line[3..]))
        } else if line.starts_with(sep) {
            Ok((BOM::NONE, line))
        } else {
            Err("Unsupported BOM")
        }
    }
}

fn parse_pri_version(line: &str) -> Result<Pri, &'static str> {
    if ! line.starts_with("<") {
        return Err("The priority should be inside brackets")
    }
    let mut parts = line[1..].splitn(2, '>');
    let pri: u8 = try!(try!(parts.next().ok_or("Empty priority")).parse().or(Err("Invalid priority")));
    let version = try!(parts.next().ok_or("Missing version"));
    if version != "1" {
        return Err("Unsupported version");
    }
    Ok(Pri::new(pri))
}

fn rfc3339_to_unix(rfc3339: &str) -> Result<i64, &'static str> {
    match DateTime::parse_from_rfc3339(rfc3339) {
        Ok(date) => Ok(date.timestamp()),
        Err(_) => Err("Unable to parse the date")
    }
}

fn parse_ts(line: &str) -> Result<i64, &'static str> {
    rfc3339_to_unix(line)
}

fn unescape_sd_value(value: &str) -> String {
    let mut res = "".to_owned();
    let mut esc = false;

    for c in value.chars() {
        match (c, esc) {
            ('\\', false) => esc = true,
            (_, false) => res.push(c),
            ('"', true) | ('\\', true) | (']', true) => {
                res.push(c);
                esc = false;
            }
            (_, true) => {
                res.push('\\');
                res.push(c);
                esc = false;
            }
        }
    }
    res
}

fn parse_msg(line: &str, offset: usize) -> Option<String> {
    if offset > line.len() {
        None
    } else {
        match line[offset..].trim() {
            "" => None,
            m @ _ => Some(m.to_owned())
        }
    }
}

fn parse_data(line: &str) -> Result<(Option<StructuredData>, Option<String>), &'static str> {
    match try!(line.chars().next().ok_or("Short message")) {
        '-' => {
            return Ok((None, parse_msg(line, 1)));
        }
        '[' => { }
        _ => return Err("Short message")
    };
    let mut parts = line[1..].splitn(2, ' ');
    let sd_id = try!(parts.next().ok_or("Missing structured data id"));
    let sd = try!(parts.next().ok_or("Missing structured data"));
    let mut in_name = false;
    let mut in_value = false;
    let mut name_start = 0;
    let mut value_start = 0;
    let mut name: Option<&str> = None;
    let mut esc = false;
    let mut after_sd: Option<usize> = None;
    let mut sd_res = StructuredData::new(sd_id);

    for (i, c) in sd.chars().enumerate() {
        let is_sd_name = match c as u32 {
            32 | 34 | 61 | 93 => false,
            33 ... 126 => true,
            _ => false
        };
        match (c, esc, is_sd_name, in_name, name.is_some(), in_value) {
            (' ', false, _, false, false, _) => { /* contextless spaces */ }
            (']', false, _, false, false, _) => {
                after_sd = Some(i + 1);
                break;
            }
            (_, false, true, false, false, _) => {
                in_name = true;
                name_start = i;
            }
            (_, _, true, true, false, _) => { /* name */ }
            ('=', false, _, true, _, _) => {
                name = Some(&sd[name_start .. i]);
                in_name = false;
            }
            ('"', false, _, _, true, false) => {
                in_value = true;
                value_start = i + 1;
            }
            ('\\', false, _, _, _, true) => esc = true,
            ('"', false, _, _, _, true) => {
                in_value = false;
                let value = unescape_sd_value(&sd[value_start .. i]);
                let pair = (format!("_{}", name.unwrap()), value);
                sd_res.pairs.push(pair);
                name = None;
            }
            (_, _, _, _, _, true) => esc = false,
            _ => return Err("Format error in the structured data")
        }
    }
    match after_sd {
        None => Err("Missing ] after structured data"),
        Some(offset) => Ok((Some(sd_res), parse_msg(sd, offset)))
    }
}
/*
pub struct Record {
    pub pri: Option<Pri>,
    pub ts: i64,
    pub hostname: String,
    pub appname: Option<String>,
    pub procid: Option<String>,
    pub msgid: Option<String>,
    pub sd: Option<StructuredData>,
    pub msg: Option<String>
}
*/
#[test]
fn test_rfc5424() {
    let msg = r#"<23>1 2015-08-05T15:53:45.637824Z testhostname appname 69 42 [origin@123 software="te\st sc\"ript" swVersion="0.0.1"] test message"#;
    let res = RFC5424.decode(msg).unwrap();
    let pri = res.pri.unwrap();
    assert!(pri.facility == 2);
    assert!(pri.severity == 7);
    assert!(res.ts == 1438790025);
    assert!(res.hostname == "testhostname");
    assert!(res.appname == Some("appname".to_owned()));
    assert!(res.procid == Some("69".to_owned()));
    assert!(res.msgid == Some("42".to_owned()));
    assert!(res.msg == Some("test message".to_owned()));
    let sd = res.sd.unwrap();
    assert!(sd.sd_id == "origin@123");
    let pairs = sd.pairs;
    assert!(pairs.iter().cloned().any(|(k, v)| k == "_software" && v == "te\\st sc\"ript"));
    assert!(pairs.iter().cloned().any(|(k, v)| k == "_swVersion" && v == "0.0.1"));
}
<commit_msg>Iterate over sd value offsets, not over the number of characters<commit_after>extern crate chrono;

use flowgger::Decoder;
use flowgger::config::Config;
use flowgger::record::{Record, Pri, StructuredData};
use self::chrono::DateTime;

#[derive(Clone)]
pub struct RFC5424;

impl Decoder for RFC5424 {
    fn new(config: &Config) -> RFC5424 {
        let _ = config;
        RFC5424
    }

    fn decode(&self, line: &str) -> Result<Record, &'static str> {
        let (bom, line) = match BOM::parse(line, "<") {
            Ok(bom_line) => bom_line,
            Err(err) => return Err(err)
        };
        let mut parts = line.splitn(7, ' ');
        let pri_version = try!(parse_pri_version(try!(parts.next().ok_or("Missing priority and version"))));
        let ts = try!(parse_ts(try!(parts.next().ok_or("Missing timestamp"))));
        let hostname = try!(parts.next().ok_or("Missing hostname"));
        let appname = try!(parts.next().ok_or("Missing application name"));
        let procid = try!(parts.next().ok_or("Missing process id"));
        let msgid = try!(parts.next().ok_or("Missing message id"));
        let (sd, msg) = try!(parse_data(try!(parts.next().ok_or("Missing message data"))));
        let record = Record {
            pri: Some(pri_version),
            ts: ts,
            hostname: hostname.to_owned(),
            appname: Some(appname.to_owned()),
            procid: Some(procid.to_owned()),
            msgid: Some(msgid.to_owned()),
            sd: sd,
            msg: msg
        };
        Ok(record)
    }
}

impl Pri {
    fn new(encoded: u8) -> Pri {
        Pri {
            facility: encoded >> 3,
            severity: encoded & 7
        }
    }
}

enum BOM {
    NONE,
    UTF8
}

impl BOM {
    fn parse<'a>(line: &'a str, sep: &str) -> Result<(BOM, &'a str), &'static str> {
        if line.starts_with("\u{feff}") {
            Ok((BOM::UTF8, &line[3..]))
        } else if line.starts_with(sep) {
            Ok((BOM::NONE, line))
        } else {
            Err("Unsupported BOM")
        }
    }
}

fn parse_pri_version(line: &str) -> Result<Pri, &'static str> {
    if ! line.starts_with("<") {
        return Err("The priority should be inside brackets")
    }
    let mut parts = line[1..].splitn(2, '>');
    let pri: u8 = try!(try!(parts.next().ok_or("Empty priority")).parse().or(Err("Invalid priority")));
    let version = try!(parts.next().ok_or("Missing version"));
    if version != "1" {
        return Err("Unsupported version");
    }
    Ok(Pri::new(pri))
}

fn rfc3339_to_unix(rfc3339: &str) -> Result<i64, &'static str> {
    match DateTime::parse_from_rfc3339(rfc3339) {
        Ok(date) => Ok(date.timestamp()),
        Err(_) => Err("Unable to parse the date")
    }
}

fn parse_ts(line: &str) -> Result<i64, &'static str> {
    rfc3339_to_unix(line)
}

fn unescape_sd_value(value: &str) -> String {
    let mut res = "".to_owned();
    let mut esc = false;

    for c in value.chars() {
        match (c, esc) {
            ('\\', false) => esc = true,
            (_, false) => res.push(c),
            ('"', true) | ('\\', true) | (']', true) => {
                res.push(c);
                esc = false;
            }
            (_, true) => {
                res.push('\\');
                res.push(c);
                esc = false;
            }
        }
    }
    res
}

fn parse_msg(line: &str, offset: usize) -> Option<String> {
    if offset > line.len() {
        None
    } else {
        match line[offset..].trim() {
            "" => None,
            m @ _ => Some(m.to_owned())
        }
    }
}

fn parse_data(line: &str) -> Result<(Option<StructuredData>, Option<String>), &'static str> {
    match try!(line.chars().next().ok_or("Short message")) {
        '-' => {
            return Ok((None, parse_msg(line, 1)));
        }
        '[' => { }
        _ => return Err("Short message")
    };
    let mut parts = line[1..].splitn(2, ' ');
    let sd_id = try!(parts.next().ok_or("Missing structured data id"));
    let sd = try!(parts.next().ok_or("Missing structured data"));
    let mut in_name = false;
    let mut in_value = false;
    let mut name_start = 0;
    let mut value_start = 0;
    let mut name: Option<&str> = None;
    let mut esc = false;
    let mut after_sd: Option<usize> = None;
    let mut sd_res = StructuredData::new(sd_id);

    for (i, c) in sd.char_indices() {
        let is_sd_name = match c as u32 {
            32 | 34 | 61 | 93 => false,
            33 ... 126 => true,
            _ => false
        };
        match (c, esc, is_sd_name, in_name, name.is_some(), in_value) {
            (' ', false, _, false, false, _) => { /* contextless spaces */ }
            (']', false, _, false, false, _) => {
                after_sd = Some(i + 1);
                break;
            }
            (_, false, true, false, false, _) => {
                in_name = true;
                name_start = i;
            }
            (_, _, true, true, false, _) => { /* name */ }
            ('=', false, _, true, _, _) => {
                name = Some(&sd[name_start .. i]);
                in_name = false;
            }
            ('"', false, _, _, true, false) => {
                in_value = true;
                value_start = i + 1;
            }
            ('\\', false, _, _, _, true) => esc = true,
            ('"', false, _, _, _, true) => {
                in_value = false;
                let value = unescape_sd_value(&sd[value_start .. i]);
                let pair = (format!("_{}", name.unwrap()), value);
                sd_res.pairs.push(pair);
                name = None;
            }
            (_, _, _, _, _, true) => esc = false,
            _ => return Err("Format error in the structured data")
        }
    }
    match after_sd {
        None => Err("Missing ] after structured data"),
        Some(offset) => Ok((Some(sd_res), parse_msg(sd, offset)))
    }
}
/*
pub struct Record {
    pub pri: Option<Pri>,
    pub ts: i64,
    pub hostname: String,
    pub appname: Option<String>,
    pub procid: Option<String>,
    pub msgid: Option<String>,
    pub sd: Option<StructuredData>,
    pub msg: Option<String>
}
*/
#[test]
fn test_rfc5424() {
    let msg = r#"<23>1 2015-08-05T15:53:45.637824Z testhostname appname 69 42 [origin@123 software="te\st sc\"ript" swVersion="0.0.1"] test message"#;
    let res = RFC5424.decode(msg).unwrap();
    let pri = res.pri.unwrap();
    assert!(pri.facility == 2);
    assert!(pri.severity == 7);
    assert!(res.ts == 1438790025);
    assert!(res.hostname == "testhostname");
    assert!(res.appname == Some("appname".to_owned()));
    assert!(res.procid == Some("69".to_owned()));
    assert!(res.msgid == Some("42".to_owned()));
    assert!(res.msg == Some("test message".to_owned()));
    let sd = res.sd.unwrap();
    assert!(sd.sd_id == "origin@123");
    let pairs = sd.pairs;
    assert!(pairs.iter().cloned().any(|(k, v)| k == "_software" && v == "te\\st sc\"ript"));
    assert!(pairs.iter().cloned().any(|(k, v)| k == "_swVersion" && v == "0.0.1"));
}
<|endoftext|>


<commit_before>// Inspired by Clang's clang-format-diff:
//
// https://github.com/llvm-mirror/clang/blob/master/tools/clang-format/clang-format-diff.py

#![deny(warnings)]

use env_logger;
#[macro_use]
extern crate failure;
use getopts;
#[macro_use]
extern crate log;
use regex;
use serde::{Deserialize, Serialize};
use serde_json as json;

use std::collections::HashSet;
use std::io::{self, BufRead};
use std::{env, process};

use regex::Regex;

/// The default pattern of files to format.
///
/// We only want to format rust files by default.
const DEFAULT_PATTERN: &str = r".*\.rs";

#[derive(Fail, Debug)]
enum FormatDiffError {
    #[fail(display = "{}", _0)]
    IncorrectOptions(#[cause] getopts::Fail),
    #[fail(display = "{}", _0)]
    IncorrectFilter(#[cause] regex::Error),
    #[fail(display = "{}", _0)]
    IoError(#[cause] io::Error),
}

impl From<getopts::Fail> for FormatDiffError {
    fn from(fail: getopts::Fail) -> Self {
        FormatDiffError::IncorrectOptions(fail)
    }
}

impl From<regex::Error> for FormatDiffError {
    fn from(err: regex::Error) -> Self {
        FormatDiffError::IncorrectFilter(err)
    }
}

impl From<io::Error> for FormatDiffError {
    fn from(fail: io::Error) -> Self {
        FormatDiffError::IoError(fail)
    }
}

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

    let mut opts = getopts::Options::new();
    opts.optflag("h", "help", "show this message");
    opts.optopt(
        "p",
        "skip-prefix",
        "skip the smallest prefix containing NUMBER slashes",
        "NUMBER",
    );
    opts.optopt(
        "f",
        "filter",
        "custom pattern selecting file paths to reformat",
        "PATTERN",
    );

    if let Err(e) = run(&opts) {
        println!("{}", opts.usage(&e.to_string()));
        process::exit(1);
    }
}

#[derive(Debug, Eq, PartialEq, Serialize, Deserialize)]
struct Range {
    file: String,
    range: [u32; 2],
}

fn run(opts: &getopts::Options) -> Result<(), FormatDiffError> {
    let matches = opts.parse(env::args().skip(1))?;

    if matches.opt_present("h") {
        println!("{}", opts.usage("usage: "));
        return Ok(());
    }

    let filter = matches
        .opt_str("f")
        .unwrap_or_else(|| DEFAULT_PATTERN.to_owned());

    let skip_prefix = matches
        .opt_str("p")
        .and_then(|p| p.parse::<u32>().ok())
        .unwrap_or(0);

    let (files, ranges) = scan_diff(io::stdin(), skip_prefix, &filter)?;

    run_rustfmt(&files, &ranges)
}

fn run_rustfmt(files: &HashSet<String>, ranges: &[Range]) -> Result<(), FormatDiffError> {
    if files.is_empty() || ranges.is_empty() {
        debug!("No files to format found");
        return Ok(());
    }

    let ranges_as_json = json::to_string(ranges).unwrap();

    debug!("Files: {:?}", files);
    debug!("Ranges: {:?}", ranges);

    let exit_status = process::Command::new("rustfmt")
        .args(files)
        .arg("--file-lines")
        .arg(ranges_as_json)
        .status()?;

    if !exit_status.success() {
        return Err(FormatDiffError::IoError(io::Error::new(
            io::ErrorKind::Other,
            format!("rustfmt failed with {}", exit_status),
        )));
    }
    Ok(())
}

/// Scans a diff from `from`, and returns the set of files found, and the ranges
/// in those files.
fn scan_diff<R>(
    from: R,
    skip_prefix: u32,
    file_filter: &str,
) -> Result<(HashSet<String>, Vec<Range>), FormatDiffError>
where
    R: io::Read,
{
    let diff_pattern = format!(r"^\+\+\+\s(?:.*?/){{{}}}(\S*)", skip_prefix);
    let diff_pattern = Regex::new(&diff_pattern).unwrap();

    let lines_pattern = Regex::new(r"^@@.*\+(\d+)(,(\d+))?").unwrap();

    let file_filter = Regex::new(&format!("^{}$", file_filter))?;

    let mut current_file = None;

    let mut files = HashSet::new();
    let mut ranges = vec![];
    for line in io::BufReader::new(from).lines() {
        let line = line.unwrap();

        if let Some(captures) = diff_pattern.captures(&line) {
            current_file = Some(captures.get(1).unwrap().as_str().to_owned());
        }

        let file = match current_file {
            Some(ref f) => &**f,
            None => continue,
        };

        // FIXME(emilio): We could avoid this most of the time if needed, but
        // it's not clear it's worth it.
        if !file_filter.is_match(file) {
            continue;
        }

        let lines_captures = match lines_pattern.captures(&line) {
            Some(captures) => captures,
            None => continue,
        };

        let start_line = lines_captures
            .get(1)
            .unwrap()
            .as_str()
            .parse::<u32>()
            .unwrap();
        let line_count = match lines_captures.get(3) {
            Some(line_count) => line_count.as_str().parse::<u32>().unwrap(),
            None => 1,
        };

        if line_count == 0 {
            continue;
        }

        let end_line = start_line + line_count - 1;
        files.insert(file.to_owned());
        ranges.push(Range {
            file: file.to_owned(),
            range: [start_line, end_line],
        });
    }

    Ok((files, ranges))
}

#[test]
fn scan_simple_git_diff() {
    const DIFF: &str = include_str!("test/bindgen.diff");
    let (files, ranges) = scan_diff(DIFF.as_bytes(), 1, r".*\.rs").expect("scan_diff failed?");

    assert!(
        files.contains("src/ir/traversal.rs"),
        "Should've matched the filter"
    );

    assert!(
        !files.contains("tests/headers/anon_enum.hpp"),
        "Shouldn't have matched the filter"
    );

    assert_eq!(
        &ranges,
        &[
            Range {
                file: "src/ir/item.rs".to_owned(),
                range: [148, 158],
            },
            Range {
                file: "src/ir/item.rs".to_owned(),
                range: [160, 170],
            },
            Range {
                file: "src/ir/traversal.rs".to_owned(),
                range: [9, 16],
            },
            Range {
                file: "src/ir/traversal.rs".to_owned(),
                range: [35, 43],
            },
        ]
    );
}
<commit_msg>Use structopt for rustfmt-format-diff arguments parsing<commit_after>// Inspired by Clang's clang-format-diff:
//
// https://github.com/llvm-mirror/clang/blob/master/tools/clang-format/clang-format-diff.py

#![deny(warnings)]

use env_logger;
#[macro_use]
extern crate failure;
#[macro_use]
extern crate log;
use regex;
use serde::{Deserialize, Serialize};
use serde_json as json;

use std::collections::HashSet;
use std::io::{self, BufRead};
use std::process;

use regex::Regex;

use structopt::clap::AppSettings;
use structopt::StructOpt;

/// The default pattern of files to format.
///
/// We only want to format rust files by default.
const DEFAULT_PATTERN: &str = r".*\.rs";

#[derive(Fail, Debug)]
enum FormatDiffError {
    #[fail(display = "{}", _0)]
    IncorrectOptions(#[cause] getopts::Fail),
    #[fail(display = "{}", _0)]
    IncorrectFilter(#[cause] regex::Error),
    #[fail(display = "{}", _0)]
    IoError(#[cause] io::Error),
}

impl From<getopts::Fail> for FormatDiffError {
    fn from(fail: getopts::Fail) -> Self {
        FormatDiffError::IncorrectOptions(fail)
    }
}

impl From<regex::Error> for FormatDiffError {
    fn from(err: regex::Error) -> Self {
        FormatDiffError::IncorrectFilter(err)
    }
}

impl From<io::Error> for FormatDiffError {
    fn from(fail: io::Error) -> Self {
        FormatDiffError::IoError(fail)
    }
}

#[derive(StructOpt, Debug)]
#[structopt(
    name = "rustfmt-format-diff",
    author = "",
    about = "",
    raw(setting = "AppSettings::DisableVersion"),
    raw(setting = "AppSettings::NextLineHelp")
)]
pub struct Opts {
    /// Skip the smallest prefix containing NUMBER slashes
    #[structopt(
        short = "p",
        long = "skip-prefix",
        value_name = "NUMBER",
        default_value = "0"
    )]
    skip_prefix: u32,

    /// Custom pattern selecting file paths to reformat
    #[structopt(
        short = "f",
        long = "filter",
        value_name = "PATTERN",
        raw(default_value = "DEFAULT_PATTERN")
    )]
    filter: String,
}

fn main() {
    env_logger::init();
    let opts = Opts::from_args();
    if let Err(e) = run(opts) {
        println!("{}", e);
        Opts::clap().print_help().expect("cannot write to stdout");
        process::exit(1);
    }
}

#[derive(Debug, Eq, PartialEq, Serialize, Deserialize)]
struct Range {
    file: String,
    range: [u32; 2],
}

fn run(opts: Opts) -> Result<(), FormatDiffError> {
    let (files, ranges) = scan_diff(io::stdin(), opts.skip_prefix, &opts.filter)?;
    run_rustfmt(&files, &ranges)
}

fn run_rustfmt(files: &HashSet<String>, ranges: &[Range]) -> Result<(), FormatDiffError> {
    if files.is_empty() || ranges.is_empty() {
        debug!("No files to format found");
        return Ok(());
    }

    let ranges_as_json = json::to_string(ranges).unwrap();

    debug!("Files: {:?}", files);
    debug!("Ranges: {:?}", ranges);

    let exit_status = process::Command::new("rustfmt")
        .args(files)
        .arg("--file-lines")
        .arg(ranges_as_json)
        .status()?;

    if !exit_status.success() {
        return Err(FormatDiffError::IoError(io::Error::new(
            io::ErrorKind::Other,
            format!("rustfmt failed with {}", exit_status),
        )));
    }
    Ok(())
}

/// Scans a diff from `from`, and returns the set of files found, and the ranges
/// in those files.
fn scan_diff<R>(
    from: R,
    skip_prefix: u32,
    file_filter: &str,
) -> Result<(HashSet<String>, Vec<Range>), FormatDiffError>
where
    R: io::Read,
{
    let diff_pattern = format!(r"^\+\+\+\s(?:.*?/){{{}}}(\S*)", skip_prefix);
    let diff_pattern = Regex::new(&diff_pattern).unwrap();

    let lines_pattern = Regex::new(r"^@@.*\+(\d+)(,(\d+))?").unwrap();

    let file_filter = Regex::new(&format!("^{}$", file_filter))?;

    let mut current_file = None;

    let mut files = HashSet::new();
    let mut ranges = vec![];
    for line in io::BufReader::new(from).lines() {
        let line = line.unwrap();

        if let Some(captures) = diff_pattern.captures(&line) {
            current_file = Some(captures.get(1).unwrap().as_str().to_owned());
        }

        let file = match current_file {
            Some(ref f) => &**f,
            None => continue,
        };

        // FIXME(emilio): We could avoid this most of the time if needed, but
        // it's not clear it's worth it.
        if !file_filter.is_match(file) {
            continue;
        }

        let lines_captures = match lines_pattern.captures(&line) {
            Some(captures) => captures,
            None => continue,
        };

        let start_line = lines_captures
            .get(1)
            .unwrap()
            .as_str()
            .parse::<u32>()
            .unwrap();
        let line_count = match lines_captures.get(3) {
            Some(line_count) => line_count.as_str().parse::<u32>().unwrap(),
            None => 1,
        };

        if line_count == 0 {
            continue;
        }

        let end_line = start_line + line_count - 1;
        files.insert(file.to_owned());
        ranges.push(Range {
            file: file.to_owned(),
            range: [start_line, end_line],
        });
    }

    Ok((files, ranges))
}

#[test]
fn scan_simple_git_diff() {
    const DIFF: &str = include_str!("test/bindgen.diff");
    let (files, ranges) = scan_diff(DIFF.as_bytes(), 1, r".*\.rs").expect("scan_diff failed?");

    assert!(
        files.contains("src/ir/traversal.rs"),
        "Should've matched the filter"
    );

    assert!(
        !files.contains("tests/headers/anon_enum.hpp"),
        "Shouldn't have matched the filter"
    );

    assert_eq!(
        &ranges,
        &[
            Range {
                file: "src/ir/item.rs".to_owned(),
                range: [148, 158],
            },
            Range {
                file: "src/ir/item.rs".to_owned(),
                range: [160, 170],
            },
            Range {
                file: "src/ir/traversal.rs".to_owned(),
                range: [9, 16],
            },
            Range {
                file: "src/ir/traversal.rs".to_owned(),
                range: [35, 43],
            },
        ]
    );
}

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

    #[test]
    fn default_options() {
        let empty: Vec<String> = vec![];
        let o = Opts::from_iter(&empty);
        assert_eq!(DEFAULT_PATTERN, o.filter);
        assert_eq!(0, o.skip_prefix);
    }

    #[test]
    fn good_options() {
        let o = Opts::from_iter(&["test", "-p", "10", "-f", r".*\.hs"]);
        assert_eq!(r".*\.hs", o.filter);
        assert_eq!(10, o.skip_prefix);
    }

    #[test]
    fn unexpected_option() {
        assert!(
            Opts::clap()
                .get_matches_from_safe(&["test", "unexpected"])
                .is_err()
        );
    }

    #[test]
    fn unexpected_flag() {
        assert!(
            Opts::clap()
                .get_matches_from_safe(&["test", "--flag"])
                .is_err()
        );
    }

    #[test]
    fn overridden_option() {
        assert!(
            Opts::clap()
                .get_matches_from_safe(&["test", "-p", "10", "-p", "20"])
                .is_err()
        );
    }

    #[test]
    fn negative_filter() {
        assert!(
            Opts::clap()
                .get_matches_from_safe(&["test", "-p", "-1"])
                .is_err()
        );
    }

}
<|endoftext|>


<commit_before>//! A window implemented by SDL2 back-end.

// External crates.
use std::mem::transmute;
use gl;
use gfx;
use device;
use sdl2;
use piston::{
    GameWindow,
    GameWindowSettings,
    event,
    keyboard,
    mouse,
};
use shader_version::opengl::OpenGL;

/// A widow implemented by SDL2 back-end.
pub struct GameWindowSDL2 {
    /// SDL window handle
    pub window: sdl2::video::Window,
    /// Allow dead code because this keeps track of the OpenGL context.
    /// Will be released on drop.
    #[allow(dead_code)]
    pub context: sdl2::video::GLContext,
    settings: GameWindowSettings,
    should_close: bool,
    last_pressed_key: Option<sdl2::keycode::KeyCode>,
}

impl GameWindowSDL2 {
    /// Creates a new game window for SDL2.
    pub fn new(opengl: &OpenGL, settings: GameWindowSettings) -> GameWindowSDL2 {
        sdl2::init(sdl2::InitEverything);
        let (major, minor) = opengl.get_major_minor();
        sdl2::video::gl_set_attribute(sdl2::video::GLContextMajorVersion, major);
        sdl2::video::gl_set_attribute(sdl2::video::GLContextMinorVersion, minor);
        sdl2::video::gl_set_attribute(
            sdl2::video::GLContextProfileMask,
            sdl2::video::ll::SDL_GL_CONTEXT_PROFILE_CORE as int
        );

        let window = sdl2::video::Window::new(
            settings.title.as_slice(),
            sdl2::video::PosCentered,
            sdl2::video::PosCentered,
            settings.size[0] as int,
            settings.size[1] as int,
            sdl2::video::OpenGL
        ).unwrap();
        if settings.fullscreen {
            window.set_fullscreen(sdl2::video::FTTrue);
        }

        let context = window.gl_create_context().unwrap();

        // Load the OpenGL function pointers
        gl::load_with(|s| unsafe {
            transmute(sdl2::video::gl_get_proc_address(s))
        });

        GameWindowSDL2 {
            settings: settings,
            should_close: false,
            last_pressed_key: None,
            window: window,
            context: context,
        }
    }

    /// Creates a gfx devince and front end.
    pub fn gfx(&self) -> (device::GlDevice, gfx::Frame) {
        let device = device::GlDevice::new(|s| unsafe {
            transmute(sdl2::video::gl_get_proc_address(s))
        });
        let (w, h) = self.get_size();
        let frame = gfx::Frame::new(w as u16, h as u16);
        (device, frame)
    }
}

impl GameWindow for GameWindowSDL2 {
    fn get_settings<'a>(&'a self) -> &'a GameWindowSettings {
        &self.settings
    }

    fn should_close(&self) -> bool {
        self.should_close
    }

    fn get_draw_size(&self) -> (u32, u32) {
        let (w, h) = self.window.get_drawable_size();
        (w as u32, h as u32)
    }

    fn close(&mut self) {
        self.should_close = true;
    }

    fn swap_buffers(&self) {
        self.window.gl_swap_window();
    }

    fn capture_cursor(&mut self, enabled: bool) {
        sdl2::mouse::set_relative_mouse_mode(enabled)
    }

    fn poll_event(&mut self) -> event::Event {
        match sdl2::event::poll_event() {
            sdl2::event::QuitEvent(_) => { self.should_close = true; },
            sdl2::event::KeyDownEvent(_, _, key, _, _) => {
                // SDL2 repeats the key down event.
                // If the event is the same as last one, ignore it.
                match self.last_pressed_key {
                    Some(x) if x == key => return self.poll_event(),
                    _ => {},
                };
                self.last_pressed_key = Some(key);

                if self.settings.exit_on_esc
                && key == sdl2::keycode::EscapeKey {
                    self.should_close = true;
                } else {
                    return event::KeyPressed(sdl2_map_key(key));
                }
            },
            sdl2::event::KeyUpEvent(_, _, key, _, _) => {
                // Reset the last pressed key.
                self.last_pressed_key = match self.last_pressed_key {
                    Some(x) if x == key => None,
                    x => x,
                };

                return event::KeyReleased(sdl2_map_key(key));
            },
            sdl2::event::MouseButtonDownEvent(_, _, _, button, _, _) => {
                return event::MouseButtonPressed(sdl2_map_mouse(button));
            },
            sdl2::event::MouseButtonUpEvent(_, _, _, button, _, _) => {
                return event::MouseButtonReleased(sdl2_map_mouse(button));
            },
            sdl2::event::MouseMotionEvent(_, _, _, _, x, y, dx, dy) => {
                return event::MouseMoved(
                    x as f64,
                    y as f64,
                    Some((dx as f64, dy as f64))
                );
            },
            sdl2::event::MouseWheelEvent(_, _, _, x, y) => {
                return event::MouseScrolled(x as f64, y as f64);
            },
            _ => {},
        }
        event::NoEvent
    }
}

pub fn sdl2_map_key(keycode: sdl2::keycode::KeyCode) -> keyboard::Key {
    use std::num::FromPrimitive;
    FromPrimitive::from_u64(keycode.code() as u64).unwrap()
}

pub fn sdl2_map_mouse(button: sdl2::mouse::Mouse) -> mouse::Button {
    match button {
        sdl2::mouse::LeftMouse => mouse::Left,
        sdl2::mouse::RightMouse => mouse::Right,
        sdl2::mouse::MiddleMouse => mouse::Middle,
        sdl2::mouse::X1Mouse => mouse::X1,
        sdl2::mouse::X2Mouse => mouse::X2,
        sdl2::mouse::UnknownMouse(_) => mouse::Unknown,
    }
}

<commit_msg>Take OpenGL version by value<commit_after>//! A window implemented by SDL2 back-end.

// External crates.
use std::mem::transmute;
use gl;
use gfx;
use device;
use sdl2;
use piston::{
    GameWindow,
    GameWindowSettings,
    event,
    keyboard,
    mouse,
};
use shader_version::opengl::OpenGL;

/// A widow implemented by SDL2 back-end.
pub struct GameWindowSDL2 {
    /// SDL window handle
    pub window: sdl2::video::Window,
    /// Allow dead code because this keeps track of the OpenGL context.
    /// Will be released on drop.
    #[allow(dead_code)]
    pub context: sdl2::video::GLContext,
    settings: GameWindowSettings,
    should_close: bool,
    last_pressed_key: Option<sdl2::keycode::KeyCode>,
}

impl GameWindowSDL2 {
    /// Creates a new game window for SDL2.
    pub fn new(opengl: OpenGL, settings: GameWindowSettings) -> GameWindowSDL2 {
        sdl2::init(sdl2::InitEverything);
        let (major, minor) = opengl.get_major_minor();
        sdl2::video::gl_set_attribute(sdl2::video::GLContextMajorVersion, major);
        sdl2::video::gl_set_attribute(sdl2::video::GLContextMinorVersion, minor);
        sdl2::video::gl_set_attribute(
            sdl2::video::GLContextProfileMask,
            sdl2::video::ll::SDL_GL_CONTEXT_PROFILE_CORE as int
        );

        let window = sdl2::video::Window::new(
            settings.title.as_slice(),
            sdl2::video::PosCentered,
            sdl2::video::PosCentered,
            settings.size[0] as int,
            settings.size[1] as int,
            sdl2::video::OpenGL
        ).unwrap();
        if settings.fullscreen {
            window.set_fullscreen(sdl2::video::FTTrue);
        }

        let context = window.gl_create_context().unwrap();

        // Load the OpenGL function pointers
        gl::load_with(|s| unsafe {
            transmute(sdl2::video::gl_get_proc_address(s))
        });

        GameWindowSDL2 {
            settings: settings,
            should_close: false,
            last_pressed_key: None,
            window: window,
            context: context,
        }
    }

    /// Creates a gfx devince and front end.
    pub fn gfx(&self) -> (device::GlDevice, gfx::Frame) {
        let device = device::GlDevice::new(|s| unsafe {
            transmute(sdl2::video::gl_get_proc_address(s))
        });
        let (w, h) = self.get_size();
        let frame = gfx::Frame::new(w as u16, h as u16);
        (device, frame)
    }
}

impl GameWindow for GameWindowSDL2 {
    fn get_settings<'a>(&'a self) -> &'a GameWindowSettings {
        &self.settings
    }

    fn should_close(&self) -> bool {
        self.should_close
    }

    fn get_draw_size(&self) -> (u32, u32) {
        let (w, h) = self.window.get_drawable_size();
        (w as u32, h as u32)
    }

    fn close(&mut self) {
        self.should_close = true;
    }

    fn swap_buffers(&self) {
        self.window.gl_swap_window();
    }

    fn capture_cursor(&mut self, enabled: bool) {
        sdl2::mouse::set_relative_mouse_mode(enabled)
    }

    fn poll_event(&mut self) -> event::Event {
        match sdl2::event::poll_event() {
            sdl2::event::QuitEvent(_) => { self.should_close = true; },
            sdl2::event::KeyDownEvent(_, _, key, _, _) => {
                // SDL2 repeats the key down event.
                // If the event is the same as last one, ignore it.
                match self.last_pressed_key {
                    Some(x) if x == key => return self.poll_event(),
                    _ => {},
                };
                self.last_pressed_key = Some(key);

                if self.settings.exit_on_esc
                && key == sdl2::keycode::EscapeKey {
                    self.should_close = true;
                } else {
                    return event::KeyPressed(sdl2_map_key(key));
                }
            },
            sdl2::event::KeyUpEvent(_, _, key, _, _) => {
                // Reset the last pressed key.
                self.last_pressed_key = match self.last_pressed_key {
                    Some(x) if x == key => None,
                    x => x,
                };

                return event::KeyReleased(sdl2_map_key(key));
            },
            sdl2::event::MouseButtonDownEvent(_, _, _, button, _, _) => {
                return event::MouseButtonPressed(sdl2_map_mouse(button));
            },
            sdl2::event::MouseButtonUpEvent(_, _, _, button, _, _) => {
                return event::MouseButtonReleased(sdl2_map_mouse(button));
            },
            sdl2::event::MouseMotionEvent(_, _, _, _, x, y, dx, dy) => {
                return event::MouseMoved(
                    x as f64,
                    y as f64,
                    Some((dx as f64, dy as f64))
                );
            },
            sdl2::event::MouseWheelEvent(_, _, _, x, y) => {
                return event::MouseScrolled(x as f64, y as f64);
            },
            _ => {},
        }
        event::NoEvent
    }
}

pub fn sdl2_map_key(keycode: sdl2::keycode::KeyCode) -> keyboard::Key {
    use std::num::FromPrimitive;
    FromPrimitive::from_u64(keycode.code() as u64).unwrap()
}

pub fn sdl2_map_mouse(button: sdl2::mouse::Mouse) -> mouse::Button {
    match button {
        sdl2::mouse::LeftMouse => mouse::Left,
        sdl2::mouse::RightMouse => mouse::Right,
        sdl2::mouse::MiddleMouse => mouse::Middle,
        sdl2::mouse::X1Mouse => mouse::X1,
        sdl2::mouse::X2Mouse => mouse::X2,
        sdl2::mouse::UnknownMouse(_) => mouse::Unknown,
    }
}

<|endoftext|>