<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") {
            // 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)
                }
            }
        }
    }
}
<commit_msg>Added tests on the parsing of lines from the git remote's reply<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>
}

impl Default for ReviewResult {
    fn default() -> ReviewResult {
        ReviewResult { push_results: Vec::new(),
                       messages: Vec::new(),
                       url: None,
                       change_id: None }
    }
}

#[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 review_result = ReviewResult::default();
    for line in push_error.lines_any() {
        debug!("error: {}", line);
        if line.starts_with("remote") {
            parse_line_from_remote(&line, &mut review_result);
        }
    }
    for line in push_output.lines_any() {
        debug!("output: {}", line);
        if line.starts_with("To") ||  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"))})
            }
        };
        review_result.push_results.push(
            PushResult{
                flag: result_flag,
                reason: String::from_str(caps.at(4).unwrap())
            })
    }
    Ok(review_result)
}

/// Parses a line returned by the remote
fn parse_line_from_remote(line: &str, review_result: &mut ReviewResult) -> () {
    // 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();
                review_result.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());
                review_result.change_id = Some(String::from_str(change_id_match.unwrap().at(1).unwrap()));
            } else {
                review_result.messages.push(cap.to_string());
            }
         },
        None => {}
    }
}

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)
                }
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::{ReviewResult, parse_line_from_remote};

    #[test]
    fn test_parse_line_from_remote() {
        test_parse_line_from_remote_with_eol("");
        // older git versions add this ANSI escape code at the end of the lines
        test_parse_line_from_remote_with_eol("\u{1b}[K");
    }

    fn test_parse_line_from_remote_with_eol(remote_msg_eol: &str) {
        let mut review_result = ReviewResult::default();

        // a random message line
        let random_msg = "A random message";
        let line1 = format!("remote: {}{}", random_msg, remote_msg_eol);
        parse_line_from_remote(&line1, &mut review_result);
        assert_eq!(random_msg, review_result.messages[0]);

        // a change URL line
        let change_id = "4bc3f44f-d81f-48a5-bd38-2c7963cb6d94";
        let change_url = format!("https://delivery.shd.chef.co/e/Chef/#/organizations/sandbox/projects/radar/changes/{}", change_id);
        let line2 = format!("remote: {}{}", change_url, remote_msg_eol);
        parse_line_from_remote(&line2, &mut review_result);
        assert_eq!(change_url, review_result.url.unwrap());
        assert_eq!(change_id, review_result.change_id.unwrap());
    }
}
<|endoftext|>


<commit_before>use events::{Event, EventHandler};
use formatter::formatter::Formatter;
use runner;
use std::io;

pub struct Simple<'a, Io: io::Write + 'a> {
    buf: &'a mut Io,
    pub name_stack: Vec<String>,
    pub failures: Vec<String>,
}

impl<'a, T: io::Write> Simple<'a, T> {
    pub fn new(buf: &mut T) -> Simple<T> {
        Simple {
            buf: buf,
            name_stack: vec![],
            failures: vec![],
        }
    }

    fn failures_summary(&self) -> String {
        let res = String::with_capacity(100);
        let mut idx = 0;
        self.failures
            .iter()
            .map(|fail| {
                idx += 1;
                format!("  {}) {}\n", idx, fail)
            })
            .fold(res, |mut acc, elt| {
                acc.push_str(&elt);
                acc
            })
    }

    fn write_summary(&mut self, result: runner::RunnerResult) -> String {
        let (res, report) = match result {
            Ok(report) => ("ok", report),
            Err(report) => ("FAILED", report),
        };

        format!("\n\ntest result: {}. {} examples; {} passed; {} failed;",
                res,
                report.total_tests,
                report.success_count,
                report.error_count)
    }
}

impl<'a, T: io::Write> EventHandler for Simple<'a, T> {
    fn trigger(&mut self, event: &Event) {
        // FIXME: do something with the io::Error ?
        let _ = match *event {
            Event::StartRunner => writeln!(self.buf, "Running tests:\n"),
            Event::StartDescribe(ref name) |
            Event::StartTest(ref name) => {
                self.name_stack.push(name.clone());
                Ok(())
            }
            Event::EndTest(result) => {
                if !self.name_stack.is_empty() {
                    let failure_name = self.name_stack.join(" | ");
                    self.failures.push(failure_name);
                    self.name_stack.pop();
                }
                let chr = if result.is_ok() {
                    "."
                } else {
                    "F"
                };
                write!(self.buf, "{}", chr)
            }
            Event::FinishedRunner(result) => {
                let res = self.write_summary(result);
                writeln!(self.buf, "{}", res)
            }
            Event::EndDescribe => {
                self.name_stack.pop();
                Ok(())
            }
            // _ => Ok(()),
        };
    }
}
impl<'a, T: io::Write> Formatter for Simple<'a, T> {}

#[cfg(test)]
mod tests {
    pub use super::*;
    pub use formatter::formatter::Formatter;
    pub use events::{Event, EventHandler};
    pub use std::io;
    pub use std::str;

    #[test]
    fn it_can_be_instanciated() {
        Simple::new(&mut vec![1u8]);
    }

    #[test]
    fn it_impl_formatter_trait() {
        let _: &Formatter = &Simple::new(&mut vec![1u8]) as &Formatter;
    }

    mod event_start_runner {
        pub use super::*;

        #[test]
        fn it_display_that_tests_started() {
            let mut v = vec![];
            {
                let mut s = Simple::new(&mut v);
                s.trigger(&Event::StartRunner);
            }

            assert_eq!("Running tests:\n\n", str::from_utf8(&v).unwrap());
        }
    }

    mod event_finished_runner {
        pub use super::*;
        use runner::TestReport;

        fn make_report(succes: u32, errors: u32) -> Result<TestReport, TestReport> {
            let mut report = TestReport::default();
            report.success_count = succes;
            report.error_count = errors;
            report.total_tests = succes + errors;

            if errors != 0 {
                Err(report)
            } else {
                Ok(report)
            }
        }

        macro_rules! test_and_compare_output {
            ($(
                $test_name:ident : (success: $succ:expr, errors: $err:expr) => $msg:expr
            ),+) => {

                $(
                    #[test]
                    fn $test_name() {
                        let mut sink = io::sink();
                        let res = {
                            let mut s = Simple::new(&mut sink);
                            s.write_summary(make_report($succ, $err))
                        };

                        assert_eq!($msg, res)
                    }
                )+
            }
        }

        test_and_compare_output! {
            no_test_is_ok: (success: 0, errors: 0) =>
                "\n\ntest result: ok. 0 examples; 0 passed; 0 failed;",
            one_test: (success: 1, errors: 0) =>
                "\n\ntest result: ok. 1 examples; 1 passed; 0 failed;",
            multiple_ok: (success: 42, errors: 0) =>
                "\n\ntest result: ok. 42 examples; 42 passed; 0 failed;",
            one_error: (success: 0, errors: 1) =>
              "\n\ntest result: FAILED. 1 examples; 0 passed; 1 failed;",
            multiple_errors: (success: 0, errors: 37) =>
              "\n\ntest result: FAILED. 37 examples; 0 passed; 37 failed;",
            one_of_each: (success: 1, errors: 1) =>
              "\n\ntest result: FAILED. 2 examples; 1 passed; 1 failed;",
            multiple_of_each: (success: 12, errors: 21) =>
              "\n\ntest result: FAILED. 33 examples; 12 passed; 21 failed;"
        }
    }

    mod event_end_test {
        pub use super::*;

        #[test]
        fn it_displays_a_dot_when_success() {
            let mut v = vec![];
            {
                let mut s = Simple::new(&mut v);
                s.trigger(&Event::EndTest(Ok(())));
            }

            assert_eq!(".", str::from_utf8(&v).unwrap());
        }

        #[test]
        #[allow(non_snake_case)]
        fn it_displays_a_F_when_error() {
            let mut v = vec![];
            {
                let mut s = Simple::new(&mut v);
                s.trigger(&Event::EndTest(Err(())));
            }

            assert_eq!("F", str::from_utf8(&v).unwrap());
        }
    }

    mod event_start_end_describe {
        pub use super::*;

        #[test]
        fn start_describe_event_push_the_name_stack() {
            let mut sink = &mut io::sink();
            let mut s = Simple::new(&mut sink);

            s.trigger(&Event::StartDescribe(String::from("Hey !")));
            assert_eq!(vec![String::from("Hey !")], s.name_stack);

            s.trigger(&Event::StartDescribe(String::from("Ho !")));
            assert_eq!(vec![String::from("Hey !"), String::from("Ho !")],
                       s.name_stack)
        }

        #[test]
        fn end_describe_event_pop_the_name_stack() {
            let mut sink = &mut io::sink();
            let mut s = Simple::new(&mut sink);

            s.trigger(&Event::StartDescribe(String::from("Hey !")));
            s.trigger(&Event::StartDescribe(String::from("Ho !")));

            s.trigger(&Event::EndDescribe);
            assert_eq!(vec![String::from("Hey !")], s.name_stack);

            s.trigger(&Event::EndDescribe);
            assert_eq!(0, s.name_stack.len());
        }
    }

    mod failures_pretty_printing {
        use super::*;

        #[test]
        fn it_register_failures() {
            let mut sink = &mut io::sink();
            let mut s = Simple::new(&mut sink);
            s.trigger(&Event::StartTest("hola".into()));
            s.trigger(&Event::EndTest(Err(())));
            assert_eq!(1, s.failures.len());
        }

        #[test]
        fn it_keep_track_of_the_failure_name() {
            let mut sink = &mut io::sink();
            let mut s = Simple::new(&mut sink);
            s.trigger(&Event::StartTest("hola".into()));
            s.trigger(&Event::EndTest(Err(())));
            assert_eq!(Some(&"hola".into()), s.failures.get(0));
        }

        #[test]
        fn it_has_a_nice_diplay_for_describes() {
            let mut sink = &mut io::sink();
            let mut s = Simple::new(&mut sink);
            s.trigger(&Event::StartDescribe("hola".into()));
            s.trigger(&Event::StartTest("holé".into()));
            s.trigger(&Event::EndTest(Err(())));
            assert_eq!(Some(&"hola | holé".into()), s.failures.get(0));

            s.trigger(&Event::StartDescribe("ohééé".into()));
            s.trigger(&Event::StartTest("holé".into()));
            s.trigger(&Event::EndTest(Err(())));
            assert_eq!(Some(&"hola | ohééé | holé".into()), s.failures.get(1));
        }

        #[test]
        fn it_works_with_multiple_describes() {
            let mut sink = &mut io::sink();
            let mut s = Simple::new(&mut sink);
            s.trigger(&Event::StartDescribe("hola".into()));
            s.trigger(&Event::StartTest("holé".into()));
            s.trigger(&Event::EndTest(Err(())));

            s.trigger(&Event::EndDescribe);
            s.trigger(&Event::StartDescribe("ok".into()));
            s.trigger(&Event::StartTest("cacao".into()));
            s.trigger(&Event::EndTest(Err(())));
            assert_eq!(Some(&"ok | cacao".into()), s.failures.get(1));
        }

        #[test]
        fn format_all_failures_one_error() {
            let mut buf = vec![];
            let res = {
                let mut s = Simple::new(&mut buf);
                s.trigger(&Event::StartDescribe("hola".into()));
                s.trigger(&Event::StartTest("holé".into()));
                s.trigger(&Event::EndTest(Err(())));
                s.failures_summary()
            };

            assert_eq!("  1) hola | holé\n", res);
        }

        #[test]
        fn format_all_failures() {
            let mut buf = vec![];
            let res = {
                let mut s = Simple::new(&mut buf);
                s.trigger(&Event::StartDescribe("hola".into()));
                s.trigger(&Event::StartTest("holé".into()));
                s.trigger(&Event::EndTest(Err(())));
                s.trigger(&Event::StartTest("hola".into()));
                s.trigger(&Event::EndTest(Err(())));
                s.failures_summary()
            };

            assert_eq!("  1) hola | holé\n  2) hola | hola\n", res);

            let res = {
                let mut s = Simple::new(&mut buf);
                s.trigger(&Event::StartDescribe("hola".into()));
                s.trigger(&Event::StartTest("holé".into()));
                s.trigger(&Event::EndTest(Err(())));
                s.trigger(&Event::EndDescribe);
                s.trigger(&Event::StartDescribe("second".into()));
                s.trigger(&Event::StartDescribe("third".into()));
                s.trigger(&Event::StartTest("hola".into()));
                s.trigger(&Event::EndTest(Err(())));
                s.failures_summary()
            };

            assert_eq!("  1) hola | holé\n  2) second | third | hola\n", res);
        }
    }
}
<commit_msg> fix some bugs + simplify some tests<commit_after>use events::{Event, EventHandler};
use formatter::formatter::Formatter;
use runner;
use std::io;

pub struct Simple<'a, Io: io::Write + 'a> {
    buf: &'a mut Io,
    pub name_stack: Vec<String>,
    pub failures: Vec<String>,
}

impl<'a, T: io::Write> Simple<'a, T> {
    pub fn new(buf: &mut T) -> Simple<T> {
        Simple {
            buf: buf,
            name_stack: vec![],
            failures: vec![],
        }
    }

    fn failures_summary(&self) -> String {
        let res = String::with_capacity(100);
        let mut idx = 0;
        self.failures
            .iter()
            .map(|fail| {
                idx += 1;
                format!("  {}) {}\n", idx, fail)
            })
            .fold(res, |mut acc, elt| {
                acc.push_str(&elt);
                acc
            })
    }

    fn write_summary(&mut self, result: runner::RunnerResult) -> String {
        let (res, report) = match result {
            Ok(report) => ("ok", report),
            Err(report) => ("FAILED", report),
        };

        format!("\n\ntest result: {}. {} examples; {} passed; {} failed;",
                res,
                report.total_tests,
                report.success_count,
                report.error_count)
    }
}

impl<'a, T: io::Write> EventHandler for Simple<'a, T> {
    fn trigger(&mut self, event: &Event) {
        // FIXME: do something with the io::Error ?
        let _ = match *event {
            Event::StartRunner => writeln!(self.buf, "Running tests:\n"),
            Event::StartDescribe(ref name) |
            Event::StartTest(ref name) => {
                self.name_stack.push(name.clone());
                Ok(())
            }
            Event::EndTest(result) => {
                if result.is_err() && !self.name_stack.is_empty() {
                    let failure_name = self.name_stack.join(" | ");
                    self.failures.push(failure_name);
                }
                self.name_stack.pop();

                let chr = if result.is_ok() {
                    "."
                } else {
                    "F"
                };
                write!(self.buf, "{}", chr)
            }
            Event::FinishedRunner(result) => {
                let failures_sum = self.failures_summary();
                let summary = self.write_summary(result);
                writeln!(self.buf,
                         "\n\nFailed examples:\n{}{}",
                         failures_sum,
                         summary)
            }
            Event::EndDescribe => {
                self.name_stack.pop();
                Ok(())
            }
            // _ => Ok(()),
        };
    }
}
impl<'a, T: io::Write> Formatter for Simple<'a, T> {}

#[cfg(test)]
mod tests {
    pub use super::*;
    pub use formatter::formatter::Formatter;
    pub use events::{Event, EventHandler};
    pub use std::io;
    pub use std::str;

    #[test]
    fn it_can_be_instanciated() {
        Simple::new(&mut vec![1u8]);
    }

    #[test]
    fn it_impl_formatter_trait() {
        let _: &Formatter = &Simple::new(&mut vec![1u8]) as &Formatter;
    }

    mod event_start_runner {
        pub use super::*;

        #[test]
        fn it_display_that_tests_started() {
            let mut v = vec![];
            {
                let mut s = Simple::new(&mut v);
                s.trigger(&Event::StartRunner);
            }

            assert_eq!("Running tests:\n\n", str::from_utf8(&v).unwrap());
        }
    }

    mod event_finished_runner {
        pub use super::*;
        use runner::TestReport;

        fn make_report(succes: u32, errors: u32) -> Result<TestReport, TestReport> {
            let mut report = TestReport::default();
            report.success_count = succes;
            report.error_count = errors;
            report.total_tests = succes + errors;

            if errors != 0 {
                Err(report)
            } else {
                Ok(report)
            }
        }

        macro_rules! test_and_compare_output {
            ($(
                $test_name:ident : (success: $succ:expr, errors: $err:expr) => $msg:expr
            ),+) => {

                $(
                    #[test]
                    fn $test_name() {
                        let mut sink = io::sink();
                        let res = {
                            let mut s = Simple::new(&mut sink);
                            s.write_summary(make_report($succ, $err))
                        };

                        assert_eq!($msg, res)
                    }
                )+
            }
        }

        test_and_compare_output! {
            no_test_is_ok: (success: 0, errors: 0) =>
                "\n\ntest result: ok. 0 examples; 0 passed; 0 failed;",
            one_test: (success: 1, errors: 0) =>
                "\n\ntest result: ok. 1 examples; 1 passed; 0 failed;",
            multiple_ok: (success: 42, errors: 0) =>
                "\n\ntest result: ok. 42 examples; 42 passed; 0 failed;",
            one_error: (success: 0, errors: 1) =>
              "\n\ntest result: FAILED. 1 examples; 0 passed; 1 failed;",
            multiple_errors: (success: 0, errors: 37) =>
              "\n\ntest result: FAILED. 37 examples; 0 passed; 37 failed;",
            one_of_each: (success: 1, errors: 1) =>
              "\n\ntest result: FAILED. 2 examples; 1 passed; 1 failed;",
            multiple_of_each: (success: 12, errors: 21) =>
              "\n\ntest result: FAILED. 33 examples; 12 passed; 21 failed;"
        }
    }

    mod event_end_test {
        pub use super::*;

        #[test]
        fn it_displays_a_dot_when_success() {
            let mut v = vec![];
            {
                let mut s = Simple::new(&mut v);
                s.trigger(&Event::EndTest(Ok(())));
            }

            assert_eq!(".", str::from_utf8(&v).unwrap());
        }

        #[test]
        #[allow(non_snake_case)]
        fn it_displays_a_F_when_error() {
            let mut v = vec![];
            {
                let mut s = Simple::new(&mut v);
                s.trigger(&Event::EndTest(Err(())));
            }

            assert_eq!("F", str::from_utf8(&v).unwrap());
        }
    }

    mod event_start_end_describe {
        pub use super::*;

        #[test]
        fn start_describe_event_push_the_name_stack() {
            let mut sink = &mut io::sink();
            let mut s = Simple::new(&mut sink);

            s.trigger(&Event::StartDescribe(String::from("Hey !")));
            assert_eq!(vec![String::from("Hey !")], s.name_stack);

            s.trigger(&Event::StartDescribe(String::from("Ho !")));
            assert_eq!(vec![String::from("Hey !"), String::from("Ho !")],
                       s.name_stack)
        }

        #[test]
        fn end_describe_event_pop_the_name_stack() {
            let mut sink = &mut io::sink();
            let mut s = Simple::new(&mut sink);

            s.trigger(&Event::StartDescribe(String::from("Hey !")));
            s.trigger(&Event::StartDescribe(String::from("Ho !")));

            s.trigger(&Event::EndDescribe);
            assert_eq!(vec![String::from("Hey !")], s.name_stack);

            s.trigger(&Event::EndDescribe);
            assert_eq!(0, s.name_stack.len());
        }
    }

    mod failures_pretty_printing {
        use super::*;

        #[test]
        fn it_register_failures() {
            let mut sink = &mut io::sink();
            let mut s = Simple::new(&mut sink);
            s.trigger(&Event::StartTest("hola".into()));
            s.trigger(&Event::EndTest(Err(())));
            assert_eq!(1, s.failures.len());
        }

        #[test]
        fn it_keep_track_of_the_failure_name() {
            let mut sink = &mut io::sink();
            let mut s = Simple::new(&mut sink);
            s.trigger(&Event::StartTest("hola".into()));
            s.trigger(&Event::EndTest(Err(())));
            assert_eq!(Some(&"hola".into()), s.failures.get(0));
        }

        #[test]
        fn it_has_a_nice_diplay_for_describes() {
            let mut sink = &mut io::sink();
            let mut s = Simple::new(&mut sink);
            s.trigger(&Event::StartDescribe("hola".into()));
            s.trigger(&Event::StartTest("holé".into()));
            s.trigger(&Event::EndTest(Err(())));
            assert_eq!(Some(&"hola | holé".into()), s.failures.get(0));

            s.trigger(&Event::StartDescribe("ohééé".into()));
            s.trigger(&Event::StartTest("holé".into()));
            s.trigger(&Event::EndTest(Err(())));
            assert_eq!(Some(&"hola | ohééé | holé".into()), s.failures.get(1));
        }

        #[test]
        fn it_works_with_multiple_describes() {
            let mut sink = &mut io::sink();
            let mut s = Simple::new(&mut sink);
            s.trigger(&Event::StartDescribe("hola".into()));
            s.trigger(&Event::StartTest("holé".into()));
            s.trigger(&Event::EndTest(Err(())));

            s.trigger(&Event::EndDescribe);
            s.trigger(&Event::StartDescribe("ok".into()));
            s.trigger(&Event::StartTest("cacao".into()));
            s.trigger(&Event::EndTest(Err(())));
            assert_eq!(Some(&"ok | cacao".into()), s.failures.get(1));
        }

        #[test]
        fn it_doesnt_includes_success() {
            let mut sink = &mut io::sink();
            let mut s = Simple::new(&mut sink);
            s.trigger(&Event::StartDescribe("hola".into()));
            s.trigger(&Event::StartTest("holé".into()));
            s.trigger(&Event::EndTest(Ok(())));

            assert_eq!(None, s.failures.get(0));
        }

        #[test]
        fn is_doesnt_keep_tests_in_name_stack() {
            let mut sink = &mut io::sink();
            let mut s = Simple::new(&mut sink);
            s.trigger(&Event::StartDescribe("hola".into()));
            s.trigger(&Event::StartTest("holé".into()));
            s.trigger(&Event::EndTest(Ok(())));
            s.trigger(&Event::StartTest("holé".into()));
            s.trigger(&Event::EndTest(Err(())));

            // not "hola | holé | holé"
            assert_eq!(Some(&"hola | holé".into()), s.failures.get(0));
        }

        #[test]
        fn format_all_failures_one_error() {
            let mut sink = &mut io::sink();
            let res = {
                let mut s = Simple::new(&mut sink);
                s.trigger(&Event::StartDescribe("hola".into()));
                s.trigger(&Event::StartTest("holé".into()));
                s.trigger(&Event::EndTest(Err(())));
                s.failures_summary()
            };

            assert_eq!("  1) hola | holé\n", res);
        }

        #[test]
        fn format_all_failures() {
            let mut sink = &mut io::sink();
            let res = {
                let mut s = Simple::new(&mut sink);
                s.trigger(&Event::StartDescribe("hola".into()));
                s.trigger(&Event::StartTest("holé".into()));
                s.trigger(&Event::EndTest(Err(())));
                s.trigger(&Event::StartTest("hola".into()));
                s.trigger(&Event::EndTest(Err(())));
                s.failures_summary()
            };

            assert_eq!("  1) hola | holé\n  2) hola | hola\n", res);

            let res = {
                let mut s = Simple::new(&mut sink);
                s.trigger(&Event::StartDescribe("hola".into()));
                s.trigger(&Event::StartTest("holé".into()));
                s.trigger(&Event::EndTest(Err(())));
                s.trigger(&Event::EndDescribe);
                s.trigger(&Event::StartDescribe("second".into()));
                s.trigger(&Event::StartDescribe("third".into()));
                s.trigger(&Event::StartTest("hola".into()));
                s.trigger(&Event::EndTest(Err(())));
                s.failures_summary()
            };

            assert_eq!("  1) hola | holé\n  2) second | third | hola\n", res);
        }
    }
}
<|endoftext|>


<commit_before>use std::boxed::Box;
use std::f64;
use std::u64;

use interface::i_spatial_accel;
use interface::i_bound::{ IBound, BoundType };

use implement::math::bound::AxisAlignedBBox;

/// implementation of spatial acceleration using bounding volume hierarchy with surface area heuristic
pub struct Bvh {
    _root: NodeBvh,
    _bins: u32,
}

///internal node structure for Bvh
pub struct NodeBvh {
    _bound: AxisAlignedBBox,
    _left: BvhBranch,
    _right: BvhBranch,
    _obj: u64, //leaf data
}

pub enum BvhBranch {
    CHILD(Box< NodeBvh >),
    EMPTY,
}

impl Default for NodeBvh {
    fn default() -> NodeBvh {
        NodeBvh {
            _bound: AxisAlignedBBox {
                _bound_lower: [ f64::NEG_INFINITY; 3 ],
                _bound_upper: [ f64::INFINITY; 3 ],
            },
            _left: BvhBranch::EMPTY,
            _right: BvhBranch::EMPTY,
            _obj: u64::MAX,
        }
    }
}

impl NodeBvh {
    pub fn init_branches( b: AxisAlignedBBox, l: BvhBranch, r: BvhBranch ) -> NodeBvh {
        NodeBvh {
            _bound: b,
            _left: l,
            _right: r,
            _obj: u64::MAX,
        }
    }
    pub fn init_leaf( b: AxisAlignedBBox, o: u64 ) -> NodeBvh {
        NodeBvh {
            _bound: b,
            _left: BvhBranch::EMPTY,
            _right: BvhBranch::EMPTY,
            _obj: o,
        }
    }
    pub fn build_node( & mut self, num_bins: u32, objs: &[ (u64, &IBound) ] ) -> Result< (), & 'static str > {
        for i in objs {
            match i.1.get_type() {
                BoundType::AxisAlignBox => (),
                _ => return Err( "unsupported bound type" )
            }
        }
        let b = objs.iter().cloned().map( |x| x.1 ).collect::<Vec< &IBound > >();
        let mut u : AxisAlignedBBox = Default::default();
        u.get_union( &b[..] );

        println!( "bound union: {:?}", u );

        //check for leaf condition
        if objs.len() == 1 {
            self._bound = u;
            self._obj = objs[0].0;
            return Ok( () )
        }

        //todo: execute alternative branch when number of objects are low
        //todo: surface area heuristic

        //get the longest axis of the bounding box
        let ( ref axis, ref length ) = u.get_longest_axis();

        self._bound = u;

        println!( "axis length: {}", length );

        let c = self._bound.get_centroid();
        let bin_len = length / num_bins as f64;

        let mut bin_offsets = vec![];
        for i in 0..num_bins {
            match axis {
                X => bin_offsets.push( i as f64 * bin_len + self._bound._bound_lower[0] ),
                Y => bin_offsets.push( i as f64 * bin_len + self._bound._bound_lower[1] ),
                Z => bin_offsets.push( i as f64 * bin_len + self._bound._bound_lower[2] ),
            }
        }

        //bin objects using their centroids in the direction of the longest axis
        let mut bins = vec![ 0; num_bins as usize ];
        let mut obj_bin = vec![];
        let epsilon = 0.001f64;
        for i in objs {
            let c = i.1.get_centroid();
            println!("centroid: {:?}", c );
            let bin_id = match axis {
                X => {
                    ( ( c[0] - self._bound._bound_lower[0] ) / length * (num_bins as f64) * ( 1.0 - epsilon ) ) as usize
                },
                Y => {
                    ( ( c[1] - self._bound._bound_lower[1] ) / length * (num_bins as f64) * ( 1.0 - epsilon ) ) as usize
                },
                Z => {
                    ( ( c[2] - self._bound._bound_lower[2] ) / length * (num_bins as f64) * ( 1.0 - epsilon ) ) as usize
                },
            };
            if bin_id >= ( num_bins as usize ) {
                println!("bin_id: {}, bin_len: {}", bin_id, num_bins );
                assert!( bin_id < ( num_bins as usize ) );
            }
            bins[bin_id] += 1;
            obj_bin.push( bin_id );
        }

        println!( "bins: {:?}", bins );

        let sum = bins.iter_mut().fold( 0, | acc, x | {
            let s = acc + *x;
            *x += acc;
            s
        }
        );
        let median = sum / 2;

        let mut split_bin_idx = 0;
        for (idx, i) in bins.iter().enumerate() {
            if *i > median {
                break;
            }
            split_bin_idx += 1;
        }

        println!( "split_bin_idx: {}", split_bin_idx );

        let mut bin_left : Vec< ( u64, &IBound ) > = vec![];
        let mut bin_right : Vec< ( u64, &IBound ) > = vec![];
        let mut idx_obj = 0;
        for i in obj_bin {
            let obj = objs[ idx_obj ];
            if i < split_bin_idx {
                bin_left.push( obj );
            } else {
                bin_right.push( obj );
            }
            idx_obj += 1;
        }

        if bin_left.len() > 0 {
            let mut l : NodeBvh = Default::default();
            println!("num left children: {}", bin_left.len() );
            l.build_node( num_bins, &bin_left[..] );
            self._left = BvhBranch::CHILD( Box::new( l ) );
        } else {
            self._left = BvhBranch::EMPTY;
        }

        if bin_right.len() > 0 {
            let mut r : NodeBvh = Default::default();
            println!("num right children: {}", bin_right.len() );
            r.build_node( num_bins, &bin_right[..] );
            self._right = BvhBranch::CHILD( Box::new( r ) );
        } else {
            self._right = BvhBranch::EMPTY;
        }

        Ok( () )
    }
}

impl i_spatial_accel::ISpatialAccel for Bvh {
    fn query( & self, input: &IBound ) -> Vec< u64 >
    {
        unimplemented!();
    }
    fn build_all( & mut self, objs: &[ (u64, &IBound) ] ) -> Result< (), & 'static str >
    {
        //initiate top down construction
        if self._bins == 0 {
            return Err( "bvh bin count cannot be zero" )
        }
        self._root.build_node( self._bins, objs )
    }
}

impl Bvh {
    pub fn init( bins: u32 ) -> Bvh {
        assert!( bins != 0 );
        Bvh {
            _root: NodeBvh {
                _bound: AxisAlignedBBox {
                    _bound_lower: [ f64::NEG_INFINITY; 3 ],
                    _bound_upper: [ f64::INFINITY; 3 ],
                },
                _left: BvhBranch::EMPTY,
                _right: BvhBranch::EMPTY,
                _obj: u64::MAX,
            },
            _bins: bins,
        }
    }
}
<commit_msg>added surface area heuristic to bvh.<commit_after>use std::boxed::Box;
use std::f64;
use std::u64;

use interface::i_spatial_accel::ISpatialAccel;
use interface::i_bound::{ IBound, BoundType };
use interface::i_stat_tree::IStatTree;

use implement::math::bound::AxisAlignedBBox;
use implement::math::bound::Axis;

/// implementation of spatial acceleration using bounding volume hierarchy with surface area heuristic
pub struct Bvh {
    _root: NodeBvh,
    _bins: u32,
}

///internal node structure for Bvh
pub struct NodeBvh {
    _bound: AxisAlignedBBox,
    _left: BvhBranch,
    _right: BvhBranch,
    _obj: u64, //leaf data
}

pub enum BvhBranch {
    CHILD(Box< NodeBvh >),
    EMPTY,
}

impl Default for NodeBvh {
    fn default() -> NodeBvh {
        NodeBvh {
            _bound: AxisAlignedBBox {
                _bound_lower: [ f64::NEG_INFINITY; 3 ],
                _bound_upper: [ f64::INFINITY; 3 ],
            },
            _left: BvhBranch::EMPTY,
            _right: BvhBranch::EMPTY,
            _obj: u64::MAX,
        }
    }
}

impl NodeBvh {
    pub fn init_branches( b: AxisAlignedBBox, l: BvhBranch, r: BvhBranch ) -> NodeBvh {
        NodeBvh {
            _bound: b,
            _left: l,
            _right: r,
            _obj: u64::MAX,
        }
    }
    pub fn init_leaf( b: AxisAlignedBBox, o: u64 ) -> NodeBvh {
        NodeBvh {
            _bound: b,
            _left: BvhBranch::EMPTY,
            _right: BvhBranch::EMPTY,
            _obj: o,
        }
    }
    pub fn build_node( & mut self, num_bins: u32, objs: &[ (u64, &IBound) ] ) -> Result< (), & 'static str > {
        for i in objs {
            match i.1.get_type() {
                BoundType::AxisAlignBox => (),
                _ => return Err( "unsupported bound type" )
            }
        }
        let b = objs.iter().cloned().map( |x| x.1 ).collect::<Vec< &IBound > >();
        let mut u : AxisAlignedBBox = Default::default();
        u.get_union( &b[..] );

        // println!( "bound union: {:?}", u );

        //check for leaf condition
        if objs.len() == 1 {
            self._bound = u;
            self._obj = objs[0].0;
            return Ok( () )
        }

        //todo: execute alternative branch when number of objects are low

        //get the longest axis of the bounding box
        let ( ref axis, ref length ) = u.get_longest_axis();

        self._bound = u;

        // println!( "axis length: {}", length );

        let c = self._bound.get_centroid();
        let bin_len = length / num_bins as f64;

        let mut bin_offsets = vec![];
        for i in 0..num_bins {
            match *axis {
                Axis::X => { bin_offsets.push( i as f64 * bin_len + self._bound._bound_lower[0] ); },
                Axis::Y => { bin_offsets.push( i as f64 * bin_len + self._bound._bound_lower[1] ); },
                Axis::Z => { bin_offsets.push( i as f64 * bin_len + self._bound._bound_lower[2] ); },
            }
        }

        //bin objects using use surface area heuristics with their entry and exit points in the long axis of bounding box
        //determine median and bin objects with their centroids
        let mut bins_surf_area = vec![ 0i64; num_bins as usize ];
        let mut obj_bin = vec![];
        let epsilon = 0.001f64;
        for i in objs {
            let c = i.1.get_centroid();
            let obj_bound_data = i.1.get_bound_data();
            let obj_entry = &obj_bound_data[0..3];
            let obj_exit = &obj_bound_data[3..6];

            // println!("centroid: {:?}", c );
            let bin_id = match *axis {
                Axis::X => {
                    let entry_bin = ( ( obj_entry[0] - self._bound._bound_lower[0] ) / length * (num_bins as f64) * ( 1.0 - epsilon ) ) as usize;
                    let exit_bin = ( ( obj_exit[0] - self._bound._bound_lower[0] ) / length * (num_bins as f64) * ( 1.0 - epsilon ) ) as usize;
                    bins_surf_area[ entry_bin ] += 1;
                    bins_surf_area[ exit_bin ] -= 1;

                    ( ( c[0] - self._bound._bound_lower[0] ) / length * (num_bins as f64) * ( 1.0 - epsilon ) ) as usize
                },
                Axis::Y => {
                    let entry_bin = ( ( obj_entry[1] - self._bound._bound_lower[1] ) / length * (num_bins as f64) * ( 1.0 - epsilon ) ) as usize;
                    let exit_bin = ( ( obj_exit[1] - self._bound._bound_lower[1] ) / length * (num_bins as f64) * ( 1.0 - epsilon ) ) as usize;
                    bins_surf_area[ entry_bin ] += 1;
                    bins_surf_area[ exit_bin ] -= 1;

                    ( ( c[1] - self._bound._bound_lower[1] ) / length * (num_bins as f64) * ( 1.0 - epsilon ) ) as usize
                },
                Axis::Z => {
                    let entry_bin = ( ( obj_entry[2] - self._bound._bound_lower[2] ) / length * (num_bins as f64) * ( 1.0 - epsilon ) ) as usize;
                    let exit_bin = ( ( obj_exit[2] - self._bound._bound_lower[2] ) / length * (num_bins as f64) * ( 1.0 - epsilon ) ) as usize;
                    bins_surf_area[ entry_bin ] += 1;
                    bins_surf_area[ exit_bin ] -= 1;

                    ( ( c[2] - self._bound._bound_lower[2] ) / length * (num_bins as f64) * ( 1.0 - epsilon ) ) as usize
                },
            };
            obj_bin.push( bin_id );
        }

        // println!( "bins surf area: {:?}", bins_surf_area );

        let _ = bins_surf_area.iter_mut().fold( 0, | acc, x | {
            *x += acc;
            *x
        }
        );

        let mut sum_surf_area = 0;
        for i in bins_surf_area.iter() {
            sum_surf_area += *i;
        }

        let median_surf_area = sum_surf_area / 2;
        // println!( "bins surf area: {:?}, sum_surf_area: {}", bins_surf_area, sum_surf_area );
        // println!( "median surf area: {:?}", median_surf_area );

        let mut split_bin_idx = 0;
        let mut accum_surf_area = 0;
        for (idx, i) in bins_surf_area.iter().enumerate() {
            accum_surf_area += *i;
            if accum_surf_area > median_surf_area {
                split_bin_idx = idx;
                break;
            }
        }

        // println!( "split_bin_idx: {}", split_bin_idx );

        let mut bin_left : Vec< ( u64, &IBound ) > = vec![];
        let mut bin_right : Vec< ( u64, &IBound ) > = vec![];
        let mut idx_obj = 0;
        for i in obj_bin {
            let obj = objs[ idx_obj ];
            if i <= split_bin_idx {
                bin_left.push( obj );
            } else {
                bin_right.push( obj );
            }
            idx_obj += 1;
        }

        if bin_left.len() > 0 {
            let mut l : NodeBvh = Default::default();
            // println!("num left children: {}", bin_left.len() );
            l.build_node( num_bins, &bin_left[..] )?;
            self._left = BvhBranch::CHILD( Box::new( l ) );
        } else {
            self._left = BvhBranch::EMPTY;
        }

        if bin_right.len() > 0 {
            let mut r : NodeBvh = Default::default();
            // println!("num right children: {}", bin_right.len() );
            r.build_node( num_bins, &bin_right[..] )?;
            self._right = BvhBranch::CHILD( Box::new( r ) );
        } else {
            self._right = BvhBranch::EMPTY;
        }

        Ok( () )
    }
}

impl ISpatialAccel for Bvh {
    fn query( & self, input: &IBound ) -> Vec< u64 >
    {
        unimplemented!();
    }
    fn build_all( & mut self, objs: &[ (u64, &IBound) ] ) -> Result< (), & 'static str >
    {
        //initiate top down construction
        if self._bins == 0 {
            return Err( "bvh bin count cannot be zero" )
        }
        self._root.build_node( self._bins, objs )
    }
}

impl Bvh {
    pub fn init( bins: u32 ) -> Bvh {
        assert!( bins != 0 );
        Bvh {
            _root: NodeBvh {
                _bound: AxisAlignedBBox {
                    _bound_lower: [ f64::NEG_INFINITY; 3 ],
                    _bound_upper: [ f64::INFINITY; 3 ],
                },
                _left: BvhBranch::EMPTY,
                _right: BvhBranch::EMPTY,
                _obj: u64::MAX,
            },
            _bins: bins,
        }
    }
}

impl IStatTree for Bvh {
    fn sum_subtree_child_count( & self ) -> Option< u64 > {
        unimplemented!();
    }
    fn node_degree( & self ) -> Option< u64 > {
        unimplemented!();
    }
    fn max_depth( & self ) -> Option< u64 > {
        unimplemented!();
    }
    fn mean_depth( & self ) -> Option< u64 > {
        unimplemented!();
    }
    fn min_depth( & self ) -> Option< u64 > {
        unimplemented!();
    }
    fn balance_ratio( & self ) -> Option< f64 > {
        unimplemented!();
    }
}
<|endoftext|>


<commit_before>use super::{ASTNode, SymbolTable};

use std::rc::Rc;
use std::borrow::Borrow;
use std::hash::Hash;

// pub type STCell<'a> = Rc<RefCell<SymbolTable<'a>>>;

pub struct Annotated<'a, T> where T: ASTNode {
    node: T, scope: Rc<SymbolTable<'a>>
}

impl<'a, T> Annotated<'a, T> where T: ASTNode {

    /// Get the type signature associated with the given name.
    ///
    /// This returns a borrowed reference to the type signature
    /// associated with that name in the current scope. The argument
    /// can be any type `Q` such that `String: Borrow<Q>` (i.e.
    /// you can pass an `&str` to this function).
    pub fn get_type<Q: ?Sized>(&self, name: &Q) -> Option<&Type>
    where String: Borrow<Q>, Q: Hash + Eq {
        self.scope.get(name)
    }

    /// Check if the given name is defined in this scope.
    ///
    /// The argument can be any type `Q` such that `String: Borrow<Q>`
    /// (i.e. you can pass an `&str` to this function).
    pub fn is_defined_here<Q: ?Sized>(&self, name: &Q) -> bool
    where String: Borrow<Q>, Q: Hash + Eq {
        self.scope.contains_key(name)
    }

}

#[derive(Debug, Clone)]
pub enum Type { Ref(Reference)
              , Prim(Primitive)
              , Algebraic //TODO: make me a thing
              , Function { params: Vec<Type>, rt: Box<Type> }
              }

/// Reference types
///
/// TODO: how will lifetime analysis actually work?
#[derive(Debug, Clone)]
pub enum Reference { Borrowed(Box<Type>) // TODO: borrowed from where?
                   , Moved(Box<Type>) // TODO: moved from where?
                   , Owned(Box<Type>)
                   , Raw(Box<Type>)
                   }

#[derive(Debug, Clone)]
pub enum Primitive { Int
                   , Uint
                   , Byte
                   , Char
                   , Str
                   , Bool
                   , Symbol(String)
                   // TODO: finish
                   }
<commit_msg>Made Boxes into Rcs in Type This should facilitate sharing of static type identifiers & negate the need for multiple unique copies of type enums. I hope.<commit_after>use super::{ASTNode, SymbolTable};

use std::rc::Rc;
use std::borrow::Borrow;
use std::hash::Hash;

// pub type STCell<'a> = Rc<RefCell<SymbolTable<'a>>>;

pub struct Annotated<'a, T> where T: ASTNode {
    node: T, scope: Rc<SymbolTable<'a>>
}

impl<'a, T> Annotated<'a, T> where T: ASTNode {

    /// Get the type signature associated with the given name.
    ///
    /// This returns a borrowed reference to the type signature
    /// associated with that name in the current scope. The argument
    /// can be any type `Q` such that `String: Borrow<Q>` (i.e.
    /// you can pass an `&str` to this function).
    pub fn get_type<Q: ?Sized>(&self, name: &Q) -> Option<&Type>
    where String: Borrow<Q>, Q: Hash + Eq {
        self.scope.get(name)
    }

    /// Check if the given name is defined in this scope.
    ///
    /// The argument can be any type `Q` such that `String: Borrow<Q>`
    /// (i.e. you can pass an `&str` to this function).
    pub fn is_defined_here<Q: ?Sized>(&self, name: &Q) -> bool
    where String: Borrow<Q>, Q: Hash + Eq {
        self.scope.contains_key(name)
    }

}

#[derive(Debug, Clone)]
pub enum Type { Ref(Reference)
              , Prim(Primitive)
              , Algebraic //TODO: make me a thing
              , Function { params: Vec<Type>
                         , rt: Rc<Type> }
              }

/// Reference types
///
/// TODO: how will lifetime analysis actually work?
#[derive(Debug, Clone)]
pub enum Reference { Borrowed(Rc<Type>) // TODO: borrowed from where?
                   , Moved(Rc<Type>) // TODO: moved from where?
                   , Owned(Rc<Type>)
                   , Raw(Rc<Type>)
                   }

#[derive(Debug, Clone)]
pub enum Primitive { Int
                   , Uint
                   , Byte
                   , Char
                   , Str
                   , Bool
                   , Symbol(String)
                   // TODO: finish
                   }
<|endoftext|>


<commit_before>use iron::prelude::*;
use base::framework::{json_error_response, json_ok_response,
                      not_found_response};
use urlencoded::UrlEncodedBody;
use base::db::MyPool;
use base::validator::{Validator, Checker, Str, StrValue, Int, IntValue, Min};
use base::framework::LoginUser;
use iron_login::User as U;
use persistent::Read;
use chrono::*;
use mysql as my;
use regex::Regex;
use regex::Captures;
use base::config::Config;
use base::constant;

pub fn new(req: &mut Request) -> IronResult<Response> {
    let mut validator = Validator::new();
    validator
        .add_checker(Checker::new("article_id", Int, "文章ID") << Min(1))
        .add_checker(Checker::new("content", Str, "内容") << Min(7));

    validator.validate(req.get::<UrlEncodedBody>());
    if !validator.is_valid() {
        return json_error_response(&validator.messages[0]);
    }

    let article_id = validator.get_valid::<IntValue>("article_id").value() as u64;

    let pool = req.get::<Read<MyPool>>().unwrap().value();
    let mut trans = pool.start_transaction(false, None, None).unwrap();

    // check whether article exists
    let raw_row = trans.prep_exec("SELECT user_id from article where id=? for update",
                                  (&article_id,)).unwrap().next();
    if raw_row.is_none() {
            return not_found_response();
    };

    let article_user_id: u64 = my::from_row(raw_row.unwrap().unwrap());

    let content = validator.get_valid::<StrValue>("content").value();
    let login = LoginUser::get_login(req);
    let user = login.get_user().unwrap();
    let now = Local::now().naive_local();

    // parse mentions such as @foo @bar
    let re = Regex::new(r"\B@([\da-zA-Z_]+)").unwrap();
    let config = req.get::<Read<Config>>().unwrap();
    let app_path = config.get("app_path").as_str().unwrap();

    let mut mentions: Vec<u64> = Vec::new();
    let new_content = re.replace_all(&content, |cap: &Captures| {
        match handle_mention(&mut trans, cap.at(1).unwrap()) {
            Some(user_id) => {
                mentions.push(user_id);
                format!("[@{}]({}{}{})",
                        cap.at(1).unwrap(),
                        app_path,
                        "/user/",
                        user_id)
            },
            None => format!("@{}", cap.at(1).unwrap()),
        }
    });

    let comment_id = trans.prep_exec(
        "INSERT INTO comment(article_id, user_id, content, create_time) \
         VALUES (?, ?, ?, ?)",
        (article_id, user.id, new_content, now)).unwrap().last_insert_id();

    trans.prep_exec("UPDATE article set comments_count=comments_count+1, \
                     update_time=? where id=?",
                    (article_id, now)).unwrap();

    // send message to article's author
    if article_user_id != user.id {
        trans.prep_exec("INSERT INTO message(article_id, comment_id, \
                         from_user_id, to_user_id, mode, \
                         status, create_time) VALUES (?, ?, ?, ?, ?, ?, ?)",
                        (article_id, comment_id, user.id, article_user_id,
                         constant::MESSAGE::MODE::REPLY_ARTICLE,
                         constant::MESSAGE::STATUS::INIT, now)).unwrap();
    }

    // send message to mentions
    mentions.sort();
    mentions.dedup();
    for mention in mentions.iter().filter(|&x| *x != article_user_id && *x != user.id) {
        trans.prep_exec("INSERT INTO message(article_id, comment_id, \
                         from_user_id, to_user_id, mode, \
                         status, create_time) VALUES (?, ?, ?, ?, ?, ?, ?)",
                        (article_id, comment_id, user.id, mention,
                         constant::MESSAGE::MODE::MENTION,
                         constant::MESSAGE::STATUS::INIT, now)).unwrap();
    }

    trans.commit().unwrap();

    json_ok_response()
}

fn handle_mention(trans: &mut my::Transaction, username: &str) -> Option<u64> {

    trans.prep_exec("SELECT id from user where username=?", (username,)).unwrap()
        .next().map(|row| my::from_row(row.unwrap()))
}
<commit_msg>fix a mysql bug when add a comment<commit_after>use iron::prelude::*;
use base::framework::{json_error_response, json_ok_response,
                      not_found_response};
use urlencoded::UrlEncodedBody;
use base::db::MyPool;
use base::validator::{Validator, Checker, Str, StrValue, Int, IntValue, Min};
use base::framework::LoginUser;
use iron_login::User as U;
use persistent::Read;
use chrono::*;
use mysql as my;
use regex::Regex;
use regex::Captures;
use base::config::Config;
use base::constant;

pub fn new(req: &mut Request) -> IronResult<Response> {
    let mut validator = Validator::new();
    validator
        .add_checker(Checker::new("article_id", Int, "文章ID") << Min(1))
        .add_checker(Checker::new("content", Str, "内容") << Min(7));

    validator.validate(req.get::<UrlEncodedBody>());
    if !validator.is_valid() {
        return json_error_response(&validator.messages[0]);
    }

    let article_id = validator.get_valid::<IntValue>("article_id").value() as u64;

    let pool = req.get::<Read<MyPool>>().unwrap().value();
    let mut trans = pool.start_transaction(false, None, None).unwrap();

    // check whether article exists
    let raw_row = trans.prep_exec("SELECT user_id from article where id=? for update",
                                  (&article_id,)).unwrap().next();
    if raw_row.is_none() {
            return not_found_response();
    };

    let article_user_id: u64 = my::from_row(raw_row.unwrap().unwrap());

    let content = validator.get_valid::<StrValue>("content").value();
    let login = LoginUser::get_login(req);
    let user = login.get_user().unwrap();
    let now = Local::now().naive_local();

    // parse mentions such as @foo @bar
    let re = Regex::new(r"\B@([\da-zA-Z_]+)").unwrap();
    let config = req.get::<Read<Config>>().unwrap();
    let app_path = config.get("app_path").as_str().unwrap();

    let mut mentions: Vec<u64> = Vec::new();
    let new_content = re.replace_all(&content, |cap: &Captures| {
        match handle_mention(&mut trans, cap.at(1).unwrap()) {
            Some(user_id) => {
                mentions.push(user_id);
                format!("[@{}]({}{}{})",
                        cap.at(1).unwrap(),
                        app_path,
                        "/user/",
                        user_id)
            },
            None => format!("@{}", cap.at(1).unwrap()),
        }
    });

    let comment_id = trans.prep_exec(
        "INSERT INTO comment(article_id, user_id, content, create_time) \
         VALUES (?, ?, ?, ?)",
        (article_id, user.id, new_content, now)).unwrap().last_insert_id();

    trans.prep_exec("UPDATE article set comments_count=comments_count+1, \
                     update_time=? where id=?",
                    (now, article_id)).unwrap();

    // send message to article's author
    if article_user_id != user.id {
        trans.prep_exec("INSERT INTO message(article_id, comment_id, \
                         from_user_id, to_user_id, mode, \
                         status, create_time) VALUES (?, ?, ?, ?, ?, ?, ?)",
                        (article_id, comment_id, user.id, article_user_id,
                         constant::MESSAGE::MODE::REPLY_ARTICLE,
                         constant::MESSAGE::STATUS::INIT, now)).unwrap();
    }

    // send message to mentions
    mentions.sort();
    mentions.dedup();
    for mention in mentions.iter().filter(|&x| *x != article_user_id && *x != user.id) {
        trans.prep_exec("INSERT INTO message(article_id, comment_id, \
                         from_user_id, to_user_id, mode, \
                         status, create_time) VALUES (?, ?, ?, ?, ?, ?, ?)",
                        (article_id, comment_id, user.id, mention,
                         constant::MESSAGE::MODE::MENTION,
                         constant::MESSAGE::STATUS::INIT, now)).unwrap();
    }

    trans.commit().unwrap();

    json_ok_response()
}

fn handle_mention(trans: &mut my::Transaction, username: &str) -> Option<u64> {

    trans.prep_exec("SELECT id from user where username=?", (username,)).unwrap()
        .next().map(|row| my::from_row(row.unwrap()))
}
<|endoftext|>