<commit_before>#[macro_use] extern crate log;
#[macro_use] extern crate nickel;
extern crate plugin;
extern crate rustc_serialize;
extern crate typemap;

use p150::{CpuState, P150Cpu};

use std::collections::HashMap;
use std::sync::{Arc,RwLock};

use nickel::{JsonBody, Nickel, Request, Response};
use nickel::{Middleware, HttpRouter, StaticFilesHandler};
use nickel::{MiddlewareResult};
use plugin::Extensible;
use rustc_serialize::hex::FromHex;
use rustc_serialize::json;
use typemap::Key;

mod p150;

static MAX_CYCLES: i32 = 100000; // maximum number of cycles CPU can execute to prevent DoS attacks.

struct CpuDb;
impl Key for CpuDb { type Value = Arc<RwLock<CpuServ>>; }

struct CpuClient {
	pub state: P150Cpu,
	pub last:  P150Cpu,
}

/// A database of multiple uniquely identified P150 CPU emulators.
///
/// Each emulator maintains its own state and can be recalled
/// using the integer identification.
struct CpuServ {
	next_cpu: i32,
	database: HashMap<i32, CpuClient>,
}

impl CpuServ {
	fn new() -> CpuServ {
		CpuServ {
			next_cpu: 0,
			database: HashMap::new(),
		}
	}
}

/// Injects a CpuServ into the current request
struct CpuMw { db: Arc<RwLock<CpuServ>> }

impl Middleware for CpuMw {
	fn invoke<'a,'b>(&'a self, req: &mut Request<'b, 'a, 'b>, res: Response<'a>) -> MiddlewareResult<'a> {
		// inject db into response
		let map = req.extensions_mut();
		map.insert::<CpuDb>(self.db.clone());

		Ok(nickel::Continue(res))
	}
}

fn main() {
	let mut server = Nickel::new();
	let mut router = Nickel::router();

	let cpus = CpuServ::new();
	let cpu_mw = CpuMw { db: Arc::new(RwLock::new(cpus)) };

	router.post("/cpu/new", cpu_new);
	router.post("/cpu/:id/tick", cpu_tick);
	router.post("/cpu/:id/back", cpu_back);
	router.post("/cpu/:id/run", cpu_run);
	router.post("/cpu/:id/load", cpu_load);
	router.get("/cpu/:id/dump", cpu_dump);
	// TODO: /cpu/:id/tick, /cpu/:id/reset
	// TODO: /cpu/:id/save ???

	router.get("/", index_show);
	router.get("/about", about_show);

	server.utilize(StaticFilesHandler::new("./public"));
	server.utilize(cpu_mw);
	server.utilize(router);

	server.listen("0.0.0.0:3042");
}

fn about_show<'a>(_req: &mut Request, res: Response<'a>) -> MiddlewareResult<'a> {
	let mut data = HashMap::new();
	data.insert("title", "About P150");
	res.render("assets/views/about.html", &data)
}

fn index_show<'a>(_req: &mut Request, res: Response<'a>) -> MiddlewareResult<'a> {
	let mut data = HashMap::new();
	data.insert("title", "P150 Emulator");
	res.render("assets/views/index.html", &data)
}

/// POST /cpu/new: creates a new CPU and adds it to the CpuServ with the next
/// available ID number.
fn cpu_new<'a>(req: &mut Request, response: Response<'a>) -> MiddlewareResult<'a> {
	match req.extensions().get::<CpuDb>() {
		Some(cpuserv) => {
			let mut w_cpuserv = cpuserv.write().unwrap();
			let cpu_no = w_cpuserv.next_cpu;

			let client = CpuClient {
				state: P150Cpu::new(),
				last:  P150Cpu::new(),
			};

			w_cpuserv.database.insert(cpu_no, client);
			w_cpuserv.next_cpu += 1;

			response.send(json::encode(&cpu_no).ok().unwrap())
		},
		None => { response.send("did not find cpu serv") }
	}
}

/// POST /cpu/:id/load
/// Overwrites the CPUs system memory with response data.
/// The current machine state is then dumped into the response as JSON.
fn cpu_load<'a>(req: &mut Request, response: Response<'a>) -> MiddlewareResult<'a> {
	let id = req.param("id").parse::<i32>().ok().expect("invalid cpu id");
	let mem_js = req.json_as::<Vec<String>>().unwrap();
	let mem_native: Vec<u8> = mem_js.iter().map(|hex| {
		match hex[..].from_hex() {
			Ok(decoded) => { if decoded.len() > 0 { decoded[0] } else { 0xFF } },
			Err(msg) => { debug!("error reading json memory: {}", msg); 0xFF },
		}
	}).collect();

	info!("length of mem instructions: {}", mem_native.len());

	// pair odd u8s with even u8s to produce u16 ops.
	let even_mn = mem_native.iter().enumerate().filter(|&(idx, _)| { idx % 2 == 0 });
	let odd_mn  = mem_native.iter().enumerate().filter(|&(idx, _)| { idx % 2 != 0 });
	let ops: Vec<u16> = even_mn.zip(odd_mn).map(|((_, &msb), (_, &lsb))| {
		((msb as u16) << 8) | (lsb as u16)
	}).collect();

	match req.extensions().get::<CpuDb>() {
		Some(cpuserv) => {
			match cpuserv.write().unwrap().database.get_mut(&id) {
				Some(cpu) => {
					cpu.state.init_mem(&ops[..]); // TODO: load from req.
					cpu.last = cpu.state;

					response.send(format!("{}", cpu.state.js_dump()))
				},
				None => { response.send("did not find the cpu") },
			}
		},

		None => { response.send("did not find cpu serv") }
	}
}

/// POST /cpu/:id/dump
/// Dumps the CPUs current state to a JSON format.
/// This request does not modify the CPU state in any way.
fn cpu_dump<'a>(req: &mut Request, response: Response<'a>) -> MiddlewareResult<'a> {
	let id = req.param("id").parse::<i32>().ok().expect("unable to dump invalid cpu id");
	match req.extensions().get::<CpuDb>() {
		Some(cpuserv) => {
			let r_serv = cpuserv.read().unwrap();
			let cpu    = r_serv.database.get(&id).unwrap();

			response.send(format!("{}", cpu.state.js_dump()))
		},
		None => { response.send("did not find cpu serv") }
	}
}

/// POST /cpu/:id/tick
/// Resumes execution from the next instruction; running the machine to completion.
/// The resultant state is then dumped to the console.
fn cpu_tick<'a>(req: &mut Request, response: Response<'a>) -> MiddlewareResult<'a> {
	let id = req.param("id").parse::<i32>().ok().expect("unable to tick invalid cpu id");

	match req.extensions().get::<CpuDb>() {
		Some(cpuserv) => {
			let mut w_serv = cpuserv.write().unwrap();
			match w_serv.database.get_mut(&id) {
				Some(cpu) => {
					cpu.last = cpu.state;
					cpu.state.tick();
					response.send(format!("{}", cpu.state.js_dump()))
				},

				None => { response.send("did not find cpu") }
			}
		},

		None => { response.send("did not find cpu serv") }
	}
}

/// POST /cpu/:id/back
/// Rewinds the CPU to the last good state.
/// States are snapshotted _before_ a /tick or /run
fn cpu_back<'a>(req: &mut Request, response: Response<'a>) -> MiddlewareResult<'a> {
	let id = req.param("id").parse::<i32>().ok().expect("unable to tick invalid cpu id");

	match req.extensions().get::<CpuDb>() {
		Some(cpuserv) => {
			let mut w_serv = cpuserv.write().unwrap();
			match w_serv.database.get_mut(&id) {
				Some(cpu) => { response.send(format!("{}", cpu.last.js_dump())) },
				None => { response.send("did not find the cpu") },
			}
		},

		None => { response.send("did not find cpu serv") },
	}
}

/// POST /cpu/:id/run
/// Resumes execution from the next instruction; running the machine to completion.
/// The resultant state is then dumped to the console.
fn cpu_run<'a>(req: &mut Request, response: Response<'a>) -> MiddlewareResult<'a> {
	let id = req.param("id").parse::<i32>().ok().expect("unable to run invalid cpu id");

	match req.extensions().get::<CpuDb>() {
		Some(cpuserv) => {
			let mut cycles = 0i32;
			let mut w_serv = cpuserv.write().unwrap();
			let cpu = w_serv.database.get_mut(&id).unwrap();
			cpu.last = cpu.state;

			loop {
				if cycles >= MAX_CYCLES { println!("CPU executed > {} cycles.", MAX_CYCLES); break; }
				cycles += 1;

				match cpu.state.tick() {
					CpuState::Halt => { break; },
					CpuState::Continue => { continue; },
				}
			}

			response.send(format!("{}", cpu.state.js_dump()))
		},
		None => { response.send("did not find cpu serv") }
	}
}
<commit_msg>update current state on rewind<commit_after>#[macro_use] extern crate log;
#[macro_use] extern crate nickel;
extern crate plugin;
extern crate rustc_serialize;
extern crate typemap;

use p150::{CpuState, P150Cpu};

use std::collections::HashMap;
use std::sync::{Arc,RwLock};

use nickel::{JsonBody, Nickel, Request, Response};
use nickel::{Middleware, HttpRouter, StaticFilesHandler};
use nickel::{MiddlewareResult};
use plugin::Extensible;
use rustc_serialize::hex::FromHex;
use rustc_serialize::json;
use typemap::Key;

mod p150;

static MAX_CYCLES: i32 = 100000; // maximum number of cycles CPU can execute to prevent DoS attacks.

struct CpuDb;
impl Key for CpuDb { type Value = Arc<RwLock<CpuServ>>; }

struct CpuClient {
	pub state: P150Cpu,
	pub last:  P150Cpu,
}

/// A database of multiple uniquely identified P150 CPU emulators.
///
/// Each emulator maintains its own state and can be recalled
/// using the integer identification.
struct CpuServ {
	next_cpu: i32,
	database: HashMap<i32, CpuClient>,
}

impl CpuServ {
	fn new() -> CpuServ {
		CpuServ {
			next_cpu: 0,
			database: HashMap::new(),
		}
	}
}

/// Injects a CpuServ into the current request
struct CpuMw { db: Arc<RwLock<CpuServ>> }

impl Middleware for CpuMw {
	fn invoke<'a,'b>(&'a self, req: &mut Request<'b, 'a, 'b>, res: Response<'a>) -> MiddlewareResult<'a> {
		// inject db into response
		let map = req.extensions_mut();
		map.insert::<CpuDb>(self.db.clone());

		Ok(nickel::Continue(res))
	}
}

fn main() {
	let mut server = Nickel::new();
	let mut router = Nickel::router();

	let cpus = CpuServ::new();
	let cpu_mw = CpuMw { db: Arc::new(RwLock::new(cpus)) };

	router.post("/cpu/new", cpu_new);
	router.post("/cpu/:id/tick", cpu_tick);
	router.post("/cpu/:id/back", cpu_back);
	router.post("/cpu/:id/run", cpu_run);
	router.post("/cpu/:id/load", cpu_load);
	router.get("/cpu/:id/dump", cpu_dump);
	// TODO: /cpu/:id/tick, /cpu/:id/reset
	// TODO: /cpu/:id/save ???

	router.get("/", index_show);
	router.get("/about", about_show);

	server.utilize(StaticFilesHandler::new("./public"));
	server.utilize(cpu_mw);
	server.utilize(router);

	server.listen("0.0.0.0:3042");
}

fn about_show<'a>(_req: &mut Request, res: Response<'a>) -> MiddlewareResult<'a> {
	let mut data = HashMap::new();
	data.insert("title", "About P150");
	res.render("assets/views/about.html", &data)
}

fn index_show<'a>(_req: &mut Request, res: Response<'a>) -> MiddlewareResult<'a> {
	let mut data = HashMap::new();
	data.insert("title", "P150 Emulator");
	res.render("assets/views/index.html", &data)
}

/// POST /cpu/new: creates a new CPU and adds it to the CpuServ with the next
/// available ID number.
fn cpu_new<'a>(req: &mut Request, response: Response<'a>) -> MiddlewareResult<'a> {
	match req.extensions().get::<CpuDb>() {
		Some(cpuserv) => {
			let mut w_cpuserv = cpuserv.write().unwrap();
			let cpu_no = w_cpuserv.next_cpu;

			let client = CpuClient {
				state: P150Cpu::new(),
				last:  P150Cpu::new(),
			};

			w_cpuserv.database.insert(cpu_no, client);
			w_cpuserv.next_cpu += 1;

			response.send(json::encode(&cpu_no).ok().unwrap())
		},
		None => { response.send("did not find cpu serv") }
	}
}

/// POST /cpu/:id/load
/// Overwrites the CPUs system memory with response data.
/// The current machine state is then dumped into the response as JSON.
fn cpu_load<'a>(req: &mut Request, response: Response<'a>) -> MiddlewareResult<'a> {
	let id = req.param("id").parse::<i32>().ok().expect("invalid cpu id");
	let mem_js = req.json_as::<Vec<String>>().unwrap();
	let mem_native: Vec<u8> = mem_js.iter().map(|hex| {
		match hex[..].from_hex() {
			Ok(decoded) => { if decoded.len() > 0 { decoded[0] } else { 0xFF } },
			Err(msg) => { debug!("error reading json memory: {}", msg); 0xFF },
		}
	}).collect();

	info!("length of mem instructions: {}", mem_native.len());

	// pair odd u8s with even u8s to produce u16 ops.
	let even_mn = mem_native.iter().enumerate().filter(|&(idx, _)| { idx % 2 == 0 });
	let odd_mn  = mem_native.iter().enumerate().filter(|&(idx, _)| { idx % 2 != 0 });
	let ops: Vec<u16> = even_mn.zip(odd_mn).map(|((_, &msb), (_, &lsb))| {
		((msb as u16) << 8) | (lsb as u16)
	}).collect();

	match req.extensions().get::<CpuDb>() {
		Some(cpuserv) => {
			match cpuserv.write().unwrap().database.get_mut(&id) {
				Some(cpu) => {
					cpu.state.init_mem(&ops[..]); // TODO: load from req.
					cpu.last = cpu.state;

					response.send(format!("{}", cpu.state.js_dump()))
				},
				None => { response.send("did not find the cpu") },
			}
		},

		None => { response.send("did not find cpu serv") }
	}
}

/// POST /cpu/:id/dump
/// Dumps the CPUs current state to a JSON format.
/// This request does not modify the CPU state in any way.
fn cpu_dump<'a>(req: &mut Request, response: Response<'a>) -> MiddlewareResult<'a> {
	let id = req.param("id").parse::<i32>().ok().expect("unable to dump invalid cpu id");
	match req.extensions().get::<CpuDb>() {
		Some(cpuserv) => {
			let r_serv = cpuserv.read().unwrap();
			let cpu    = r_serv.database.get(&id).unwrap();

			response.send(format!("{}", cpu.state.js_dump()))
		},
		None => { response.send("did not find cpu serv") }
	}
}

/// POST /cpu/:id/tick
/// Resumes execution from the next instruction; running the machine to completion.
/// The resultant state is then dumped to the console.
fn cpu_tick<'a>(req: &mut Request, response: Response<'a>) -> MiddlewareResult<'a> {
	let id = req.param("id").parse::<i32>().ok().expect("unable to tick invalid cpu id");

	match req.extensions().get::<CpuDb>() {
		Some(cpuserv) => {
			let mut w_serv = cpuserv.write().unwrap();
			match w_serv.database.get_mut(&id) {
				Some(cpu) => {
					cpu.last = cpu.state;
					cpu.state.tick();
					response.send(format!("{}", cpu.state.js_dump()))
				},

				None => { response.send("did not find cpu") }
			}
		},

		None => { response.send("did not find cpu serv") }
	}
}

/// POST /cpu/:id/back
/// Rewinds the CPU to the last good state.
/// States are snapshotted _before_ a /tick or /run
fn cpu_back<'a>(req: &mut Request, response: Response<'a>) -> MiddlewareResult<'a> {
	let id = req.param("id").parse::<i32>().ok().expect("unable to tick invalid cpu id");

	match req.extensions().get::<CpuDb>() {
		Some(cpuserv) => {
			let mut w_serv = cpuserv.write().unwrap();
			match w_serv.database.get_mut(&id) {
				Some(cpu) => {
					cpu.state = cpu.last;
					response.send(format!("{}", cpu.state.js_dump()))
				},
				None => { response.send("did not find the cpu") },
			}
		},

		None => { response.send("did not find cpu serv") },
	}
}

/// POST /cpu/:id/run
/// Resumes execution from the next instruction; running the machine to completion.
/// The resultant state is then dumped to the console.
fn cpu_run<'a>(req: &mut Request, response: Response<'a>) -> MiddlewareResult<'a> {
	let id = req.param("id").parse::<i32>().ok().expect("unable to run invalid cpu id");

	match req.extensions().get::<CpuDb>() {
		Some(cpuserv) => {
			let mut cycles = 0i32;
			let mut w_serv = cpuserv.write().unwrap();
			let cpu = w_serv.database.get_mut(&id).unwrap();
			cpu.last = cpu.state;

			loop {
				if cycles >= MAX_CYCLES { println!("CPU executed > {} cycles.", MAX_CYCLES); break; }
				cycles += 1;

				match cpu.state.tick() {
					CpuState::Halt => { break; },
					CpuState::Continue => { continue; },
				}
			}

			response.send(format!("{}", cpu.state.js_dump()))
		},
		None => { response.send("did not find cpu serv") }
	}
}
<|endoftext|>


<commit_before>#[macro_use]
extern crate log;
extern crate env_logger;
extern crate irc;
#[macro_use]
extern crate lazy_static;
extern crate regex;
extern crate hyper;
extern crate hubcaps;
extern crate hyper_native_tls;
extern crate serde_json;

use std::env;
use std::fmt;
use std::thread;
use std::collections::HashMap;
use std::ascii::AsciiExt;
use regex::Regex;

use irc::client::prelude::*;
use irc::client::data::command::Command;

use hyper::Client;
use hyper::net::HttpsConnector;
use hyper_native_tls::NativeTlsClient;
use hubcaps::{Credentials, Github};
use hubcaps::comments::CommentOptions;

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

    let config_file =
        {
            let mut args = env::args_os().skip(1); // skip program name
            let config_file = args.next().expect("Expected a single command-line argument, the JSON configuration file.");
            if args.next().is_some() {
                panic!("Expected only a single command-line argument, the JSON configuration file.");
            }
            config_file
        };

    let server = IrcServer::new(config_file).expect("Couldn't initialize server with given configuration file");
    server.identify().unwrap();

    let options = server
        .config()
        .options
        .as_ref()
        .expect("No options property within configuration?");

    // FIXME: Add a way to ask the bot to reboot itself?
    let mut channel_data: HashMap<String, ChannelData> = HashMap::new();

    for message in server.iter() {
        let message = message.unwrap(); // panic if there's an error

        match message.command {
            Command::PRIVMSG(ref target, ref msg) => {
                match message.source_nickname() {
                    None => {
                        warn!("PRIVMSG without a source! {}",
                              format!("{}", message).trim());
                    }
                    Some(ref source) => {
                        let source_ = String::from(*source);
                        let line = if msg.starts_with("\x01ACTION ") && msg.ends_with("\x01") {
                            ChannelLine {
                                source: source_,
                                is_action: true,
                                message: String::from(&msg[8..msg.len() - 1]),
                            }
                        } else {
                            ChannelLine {
                                source: source_,
                                is_action: false,
                                message: msg.clone(),
                            }
                        };
                        let mynick = server.current_nickname();
                        if target == mynick {
                            // An actual private message.
                            info!("[{}] {}", source, line);
                            handle_bot_command(&server,
                                               options,
                                               &mut channel_data,
                                               &line.message,
                                               source,
                                               false,
                                               None)
                        } else if target.starts_with('#') {
                            // A message in a channel.
                            info!("[{}] {}", target, line);
                            match check_command_in_channel(mynick, &line.message) {
                                Some(ref command) => {
                                    handle_bot_command(&server,
                                                       options,
                                                       &mut channel_data,
                                                       command,
                                                       target,
                                                       line.is_action,
                                                       Some(source))
                                }
                                None => {
                                    // FIXME: code duplication
                                    let this_channel_data =
                                        channel_data
                                            .entry(target.clone())
                                            .or_insert_with(|| ChannelData::new(options));
                                    if let Some(response) = this_channel_data.add_line(line) {
                                        server.send_privmsg(target, &*response).unwrap();
                                    }
                                }
                            }
                        } else {
                            warn!("UNEXPECTED TARGET {} in message {}",
                                  target,
                                  format!("{}", message).trim());
                        }
                    }
                }
            }
            Command::INVITE(ref target, ref channel) => {
                if target == server.current_nickname() {
                    // Join channels when invited.
                    server.send_join(channel).unwrap();
                }
            }
            _ => (),
        }
    }
}

// Take a message in the channel, and see if it was a message sent to
// this bot.
fn check_command_in_channel(mynick: &str, msg: &String) -> Option<String> {
    if !msg.starts_with(mynick) {
        return None;
    }
    let after_nick = &msg[mynick.len()..];
    if !after_nick.starts_with(":") && !after_nick.starts_with(",") {
        return None;
    }
    let after_punct = &after_nick[1..];
    Some(String::from(after_punct.trim_left()))
}

fn handle_bot_command<'opts>(server: &IrcServer,
                             options: &'opts HashMap<String, String>,
                             all_channel_data: &mut HashMap<String, ChannelData<'opts>>,
                             command: &str,
                             response_target: &str,
                             response_is_action: bool,
                             response_username: Option<&str>) {

    let send_line = |response_username: Option<&str>, line: &str| {
        let line_with_nick = match response_username {
            None => String::from(line),
            Some(username) => String::from(username) + ", " + line,
        };
        let adjusted_line = if response_is_action {
            format!("\x01ACTION {}\x01", line_with_nick)
        } else {
            line_with_nick
        };
        server
            .send_privmsg(response_target, &adjusted_line)
            .unwrap();
    };

    if command == "help" {
        send_line(response_username, "The commands I understand are:");
        send_line(None, "  help     Send this message.");
        send_line(None, "  intro    Send a message describing what I do.");
        send_line(None, "  status   Send a message with current bot status.");
        send_line(None,
                  "  bye      Leave the channel.  (You can /invite me back.)");
        return;
    }

    if command == "intro" {
        let config = server.config();
        send_line(None,
                  "My job is to leave comments in github when the group discusses github issues and takes minutes in IRC.");
        send_line(None,
                  "I separate discussions by the \"Topic:\" lines, and I know what github issues to use only by lines of the form \"GitHub topic: <url> | none\".");
        send_line(None,
                  &*format!("I'm only allowed to comment on issues in the repositories: {}.",
                            options["github_repos_allowed"]));
        let owners = if let Some(v) = config.owners.as_ref() {
            v.join(" ")
        } else {
            String::from("")
        };
        send_line(None,
                  &*format!("My source code is at {} and I'm run by {}.",
                            options["source"],
                            owners));
        return;
    }

    if command == "status" {
        // FIXME: Give the changeset we were compiled from.
        send_line(response_username,
                  "I currently have data for the following channels:");
        let mut sorted_channels: Vec<&String> = all_channel_data.keys().collect();
        sorted_channels.sort();
        for channel in sorted_channels {
            let ref channel_data = all_channel_data[channel];
            if let Some(ref topic) = channel_data.current_topic {
                send_line(None,
                          &*format!("  {} ({} lines buffered on \"{}\")",
                                    channel,
                                    topic.lines.len(),
                                    topic.topic));
                match topic.github_url {
                    None => send_line(None, "    no GitHub URL to comment on"),
                    Some(ref github_url) => {
                        send_line(None, &*format!("    will comment on {}", github_url))
                    }
                };
            } else {
                send_line(None, &*format!("  {} (no topic data buffered)", channel));
            }
        }
        return;
    }

    if command == "bye" {
        if response_target.starts_with('#') {
            // FIXME: code duplication
            let this_channel_data = all_channel_data
                .entry(String::from(response_target))
                .or_insert_with(|| ChannelData::new(options));
            this_channel_data.end_topic();
            server.send(Command::PART(String::from(response_target),
                        Some(format!("Leaving at request of {}.  Feel free to /invite me back.",
                                     response_username.unwrap())))).unwrap();
        } else {
            send_line(response_username, "'bye' only works in a channel");
        }
        return;
    }

    send_line(response_username,
              "Sorry, I don't understand that command.  Try 'help'.");
}

struct ChannelLine {
    source: String,
    is_action: bool,
    message: String,
}

struct TopicData {
    topic: String,
    github_url: Option<String>,
    lines: Vec<ChannelLine>,
    resolutions: Vec<String>,
}

struct ChannelData<'opts> {
    current_topic: Option<TopicData>,
    options: &'opts HashMap<String, String>,
}

impl fmt::Display for ChannelLine {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        if self.is_action {
            write!(f, "* {} {}", self.source, self.message)
        } else {
            write!(f, "<{}> {}", self.source, self.message)
        }
    }
}

impl TopicData {
    fn new(topic: &str) -> TopicData {
        let topic_ = String::from(topic);
        TopicData {
            topic: topic_,
            github_url: None,
            lines: vec![],
            resolutions: vec![],
        }
    }
}

impl fmt::Display for TopicData {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        if self.resolutions.len() == 0 {
            try!(write!(f,
                        "The CSS Working Group just discussed {}.\n\n",
                        // FIXME: escape self.topic
                        self.topic));
        } else {
            try!(write!(f,
                        "The CSS Working Group just discussed {}, and agreed to the following resolutions:\n\n",
                        self.topic));
            for resolution in &self.resolutions {
                try!(write!(f, "```\n{}\n```\n\n", resolution));
            }
        }

        try!(write!(f,
                    "The full IRC log of that discussion\n"));
        try!(write!(f, "\n```\n"));
        for line in &self.lines {
            try!(write!(f, "{}\n", line));
        }
        try!(write!(f, "```\n\n"));
        Ok(())
    }
}

/// A case-insensitive version of starts_with.
fn ci_starts_with(s: &str, prefix: &str) -> bool {
    assert!(prefix.to_lowercase() == prefix);
    assert!(prefix.len() == prefix.chars().count());

    s.len() >= prefix.len() &&
    prefix
        .as_bytes()
        .eq_ignore_ascii_case(&s.as_bytes()[..prefix.len()])
}

/// Remove a case-insensitive start of the line, and if that prefix is
/// present return the rest of the line.
fn strip_ci_prefix(s: &str, prefix: &str) -> Option<String> {
    if ci_starts_with(s, prefix) {
        Some(String::from(s[prefix.len()..].trim_left()))
    } else {
        None
    }
}

impl<'opts> ChannelData<'opts> {
    fn new(options_: &'opts HashMap<String, String>) -> ChannelData {
        ChannelData {
            current_topic: None,
            options: options_,
        }
    }

    // Returns the response that should be sent to the message over IRC.
    fn add_line(&mut self, line: ChannelLine) -> Option<String> {
        if let Some(ref topic) = strip_ci_prefix(&line.message, "topic:") {
            self.start_topic(topic);
        }
        if line.source == "trackbot" && line.is_action == true &&
           line.message == "is ending a teleconference." {
            self.end_topic();
        }
        match self.current_topic {
            None => None,
            Some(ref mut data) => {
                let new_url_option = extract_github_url(&line.message, self.options);
                let response = match (new_url_option.as_ref(), &data.github_url) {
                    (None, _) => None,
                    (Some(&None), &None) => None,
                    (Some(&None), _) => Some(String::from("OK, I won't post this discussion to GitHub")),
                    (Some(&Some(ref new_url)), &None) => {
                        Some(format!("OK, I'll post this discussion to {}", new_url))
                    }
                    (Some(new_url), old_url) if *old_url == *new_url => None,
                    (Some(&Some(ref new_url)), &Some(ref old_url)) => {
                        Some(format!("OK, I'll post this discussion to {} instead of {} like you said before",
                                     new_url,
                                     old_url))
                    }
                };

                if let Some(new_url) = new_url_option {
                    data.github_url = new_url;
                }

                if !line.is_action {
                    if line.message.starts_with("RESOLUTION") ||
                       line.message.starts_with("RESOLVED") {
                        data.resolutions.push(line.message.clone());
                    }

                    data.lines.push(line);
                }

                response
            }
        }
    }

    fn start_topic(&mut self, topic: &str) {
        self.end_topic();
        self.current_topic = Some(TopicData::new(topic));
    }

    fn end_topic(&mut self) {
        // TODO: Test the topic boundary code.
        if let Some(topic) = self.current_topic.take() {
            if topic.github_url.is_some() {
                let task = GithubCommentTask::new(topic, self.options);
                task.run();
            }
        }
    }
}

fn extract_github_url(message: &str, options: &HashMap<String, String>) -> Option<Option<String>> {
    lazy_static! {
        static ref GITHUB_URL_RE: Regex =
            Regex::new(r"^https://github.com/(?P<repo>[^/]*/[^/]*)/issues/(?P<number>[0-9]+)$")
            .unwrap();
    }
    let ref allowed_repos = options["github_repos_allowed"];
    if let Some(ref maybe_url) = strip_ci_prefix(&message, "github topic:") {
        if maybe_url.to_lowercase() == "none" {
            return Some(None);
        } else if let Some(ref caps) = GITHUB_URL_RE.captures(maybe_url) {
            for repo in allowed_repos.split_whitespace() {
                if caps["repo"] == *repo {
                    return Some(Some(maybe_url.clone()));
                }
            }
        }
    }
    None
}

struct GithubCommentTask {
    data: TopicData,
    github: Github,
}

impl GithubCommentTask {
    fn new(data_: TopicData, options: &HashMap<String, String>) -> GithubCommentTask {
        let github_ =
            Github::new(&*options["github_uastring"],
                        Client::with_connector(HttpsConnector::new(NativeTlsClient::new()
                                                                       .unwrap())),
                        Credentials::Token(options["github_access_token"].clone()));
        GithubCommentTask {
            data: data_,
            github: github_,
        }
    }
    fn run(self) {
        thread::spawn(move || { self.main(); });
    }
    fn main(&self) {
        lazy_static! {
            static ref GITHUB_URL_RE: Regex =
                Regex::new(r"^https://github.com/(?P<owner>[^/]*)/(?P<repo>[^/]*)/issues/(?P<number>[0-9]+)$")
                .unwrap();
        }

        if let Some(ref github_url) = self.data.github_url {
            if let Some(ref caps) = GITHUB_URL_RE.captures(github_url) {
                let repo = self.github
                    .repo(String::from(&caps["owner"]), String::from(&caps["repo"]));
                let issue = repo.issue(caps["number"].parse::<u64>().unwrap());
                let comments = issue.comments();

                let comment_text = format!("{}", self.data);
                comments
                    .create(&CommentOptions { body: comment_text })
                    .unwrap();
            } else {
                warn!("How does {} fail to match now when it matched before?",
                      github_url)
            }
        }
    }
}
<commit_msg>Fix the code duplication of creating new channel data entries.<commit_after>#[macro_use]
extern crate log;
extern crate env_logger;
extern crate irc;
#[macro_use]
extern crate lazy_static;
extern crate regex;
extern crate hyper;
extern crate hubcaps;
extern crate hyper_native_tls;
extern crate serde_json;

use std::env;
use std::fmt;
use std::thread;
use std::collections::HashMap;
use std::ascii::AsciiExt;
use regex::Regex;

use irc::client::prelude::*;
use irc::client::data::command::Command;

use hyper::Client;
use hyper::net::HttpsConnector;
use hyper_native_tls::NativeTlsClient;
use hubcaps::{Credentials, Github};
use hubcaps::comments::CommentOptions;

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

    let config_file =
        {
            let mut args = env::args_os().skip(1); // skip program name
            let config_file = args.next().expect("Expected a single command-line argument, the JSON configuration file.");
            if args.next().is_some() {
                panic!("Expected only a single command-line argument, the JSON configuration file.");
            }
            config_file
        };

    let server = IrcServer::new(config_file).expect("Couldn't initialize server with given configuration file");
    server.identify().unwrap();

    let options = server
        .config()
        .options
        .as_ref()
        .expect("No options property within configuration?");

    // FIXME: Add a way to ask the bot to reboot itself?
    let mut irc_state = IRCState::new();

    for message in server.iter() {
        let message = message.unwrap(); // panic if there's an error

        match message.command {
            Command::PRIVMSG(ref target, ref msg) => {
                match message.source_nickname() {
                    None => {
                        warn!("PRIVMSG without a source! {}",
                              format!("{}", message).trim());
                    }
                    Some(ref source) => {
                        let source_ = String::from(*source);
                        let line = if msg.starts_with("\x01ACTION ") && msg.ends_with("\x01") {
                            ChannelLine {
                                source: source_,
                                is_action: true,
                                message: String::from(&msg[8..msg.len() - 1]),
                            }
                        } else {
                            ChannelLine {
                                source: source_,
                                is_action: false,
                                message: msg.clone(),
                            }
                        };
                        let mynick = server.current_nickname();
                        if target == mynick {
                            // An actual private message.
                            info!("[{}] {}", source, line);
                            handle_bot_command(&server,
                                               options,
                                               &mut irc_state,
                                               &line.message,
                                               source,
                                               false,
                                               None)
                        } else if target.starts_with('#') {
                            // A message in a channel.
                            info!("[{}] {}", target, line);
                            match check_command_in_channel(mynick, &line.message) {
                                Some(ref command) => {
                                    handle_bot_command(&server,
                                                       options,
                                                       &mut irc_state,
                                                       command,
                                                       target,
                                                       line.is_action,
                                                       Some(source))
                                }
                                None => {
                                    let this_channel_data = irc_state.channel_data(target, options);
                                    if let Some(response) = this_channel_data.add_line(line) {
                                        server.send_privmsg(target, &*response).unwrap();
                                    }
                                }
                            }
                        } else {
                            warn!("UNEXPECTED TARGET {} in message {}",
                                  target,
                                  format!("{}", message).trim());
                        }
                    }
                }
            }
            Command::INVITE(ref target, ref channel) => {
                if target == server.current_nickname() {
                    // Join channels when invited.
                    server.send_join(channel).unwrap();
                }
            }
            _ => (),
        }
    }
}

// Take a message in the channel, and see if it was a message sent to
// this bot.
fn check_command_in_channel(mynick: &str, msg: &String) -> Option<String> {
    if !msg.starts_with(mynick) {
        return None;
    }
    let after_nick = &msg[mynick.len()..];
    if !after_nick.starts_with(":") && !after_nick.starts_with(",") {
        return None;
    }
    let after_punct = &after_nick[1..];
    Some(String::from(after_punct.trim_left()))
}

fn handle_bot_command<'opts>(server: &IrcServer,
                             options: &'opts HashMap<String, String>,
                             irc_state: &mut IRCState<'opts>,
                             command: &str,
                             response_target: &str,
                             response_is_action: bool,
                             response_username: Option<&str>) {

    let send_line = |response_username: Option<&str>, line: &str| {
        let line_with_nick = match response_username {
            None => String::from(line),
            Some(username) => String::from(username) + ", " + line,
        };
        let adjusted_line = if response_is_action {
            format!("\x01ACTION {}\x01", line_with_nick)
        } else {
            line_with_nick
        };
        server
            .send_privmsg(response_target, &adjusted_line)
            .unwrap();
    };

    if command == "help" {
        send_line(response_username, "The commands I understand are:");
        send_line(None, "  help     Send this message.");
        send_line(None, "  intro    Send a message describing what I do.");
        send_line(None, "  status   Send a message with current bot status.");
        send_line(None,
                  "  bye      Leave the channel.  (You can /invite me back.)");
        return;
    }

    if command == "intro" {
        let config = server.config();
        send_line(None,
                  "My job is to leave comments in github when the group discusses github issues and takes minutes in IRC.");
        send_line(None,
                  "I separate discussions by the \"Topic:\" lines, and I know what github issues to use only by lines of the form \"GitHub topic: <url> | none\".");
        send_line(None,
                  &*format!("I'm only allowed to comment on issues in the repositories: {}.",
                            options["github_repos_allowed"]));
        let owners = if let Some(v) = config.owners.as_ref() {
            v.join(" ")
        } else {
            String::from("")
        };
        send_line(None,
                  &*format!("My source code is at {} and I'm run by {}.",
                            options["source"],
                            owners));
        return;
    }

    if command == "status" {
        // FIXME: Give the changeset we were compiled from.
        send_line(response_username,
                  "I currently have data for the following channels:");
        let mut sorted_channels: Vec<&String> = irc_state.channel_data.keys().collect();
        sorted_channels.sort();
        for channel in sorted_channels {
            let ref channel_data = irc_state.channel_data[channel];
            if let Some(ref topic) = channel_data.current_topic {
                send_line(None,
                          &*format!("  {} ({} lines buffered on \"{}\")",
                                    channel,
                                    topic.lines.len(),
                                    topic.topic));
                match topic.github_url {
                    None => send_line(None, "    no GitHub URL to comment on"),
                    Some(ref github_url) => {
                        send_line(None, &*format!("    will comment on {}", github_url))
                    }
                };
            } else {
                send_line(None, &*format!("  {} (no topic data buffered)", channel));
            }
        }
        return;
    }

    if command == "bye" {
        if response_target.starts_with('#') {
            let this_channel_data = irc_state.channel_data(response_target, options);
            this_channel_data.end_topic();
            server.send(Command::PART(String::from(response_target),
                        Some(format!("Leaving at request of {}.  Feel free to /invite me back.",
                                     response_username.unwrap())))).unwrap();
        } else {
            send_line(response_username, "'bye' only works in a channel");
        }
        return;
    }

    send_line(response_username,
              "Sorry, I don't understand that command.  Try 'help'.");
}

struct IRCState<'opts> {
    channel_data: HashMap<String, ChannelData<'opts>>,
}

impl<'opts> IRCState<'opts> {
    fn new() -> IRCState<'opts> {
        IRCState { channel_data: HashMap::new() }
    }

    fn channel_data(&mut self,
                    channel: &str,
                    options: &'opts HashMap<String, String>)
                    -> &mut ChannelData<'opts> {
        self.channel_data
            .entry(String::from(channel))
            .or_insert_with(|| ChannelData::new(options))
    }
}

struct ChannelLine {
    source: String,
    is_action: bool,
    message: String,
}

struct TopicData {
    topic: String,
    github_url: Option<String>,
    lines: Vec<ChannelLine>,
    resolutions: Vec<String>,
}

struct ChannelData<'opts> {
    current_topic: Option<TopicData>,
    options: &'opts HashMap<String, String>,
}

impl fmt::Display for ChannelLine {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        if self.is_action {
            write!(f, "* {} {}", self.source, self.message)
        } else {
            write!(f, "<{}> {}", self.source, self.message)
        }
    }
}

impl TopicData {
    fn new(topic: &str) -> TopicData {
        let topic_ = String::from(topic);
        TopicData {
            topic: topic_,
            github_url: None,
            lines: vec![],
            resolutions: vec![],
        }
    }
}

impl fmt::Display for TopicData {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        if self.resolutions.len() == 0 {
            try!(write!(f,
                        "The CSS Working Group just discussed {}.\n\n",
                        // FIXME: escape self.topic
                        self.topic));
        } else {
            try!(write!(f,
                        "The CSS Working Group just discussed {}, and agreed to the following resolutions:\n\n",
                        self.topic));
            for resolution in &self.resolutions {
                try!(write!(f, "```\n{}\n```\n\n", resolution));
            }
        }

        try!(write!(f,
                    "The full IRC log of that discussion\n"));
        try!(write!(f, "\n```\n"));
        for line in &self.lines {
            try!(write!(f, "{}\n", line));
        }
        try!(write!(f, "```\n\n"));
        Ok(())
    }
}

/// A case-insensitive version of starts_with.
fn ci_starts_with(s: &str, prefix: &str) -> bool {
    assert!(prefix.to_lowercase() == prefix);
    assert!(prefix.len() == prefix.chars().count());

    s.len() >= prefix.len() &&
    prefix
        .as_bytes()
        .eq_ignore_ascii_case(&s.as_bytes()[..prefix.len()])
}

/// Remove a case-insensitive start of the line, and if that prefix is
/// present return the rest of the line.
fn strip_ci_prefix(s: &str, prefix: &str) -> Option<String> {
    if ci_starts_with(s, prefix) {
        Some(String::from(s[prefix.len()..].trim_left()))
    } else {
        None
    }
}

impl<'opts> ChannelData<'opts> {
    fn new(options_: &'opts HashMap<String, String>) -> ChannelData {
        ChannelData {
            current_topic: None,
            options: options_,
        }
    }

    // Returns the response that should be sent to the message over IRC.
    fn add_line(&mut self, line: ChannelLine) -> Option<String> {
        if let Some(ref topic) = strip_ci_prefix(&line.message, "topic:") {
            self.start_topic(topic);
        }
        if line.source == "trackbot" && line.is_action == true &&
           line.message == "is ending a teleconference." {
            self.end_topic();
        }
        match self.current_topic {
            None => None,
            Some(ref mut data) => {
                let new_url_option = extract_github_url(&line.message, self.options);
                let response = match (new_url_option.as_ref(), &data.github_url) {
                    (None, _) => None,
                    (Some(&None), &None) => None,
                    (Some(&None), _) => Some(String::from("OK, I won't post this discussion to GitHub")),
                    (Some(&Some(ref new_url)), &None) => {
                        Some(format!("OK, I'll post this discussion to {}", new_url))
                    }
                    (Some(new_url), old_url) if *old_url == *new_url => None,
                    (Some(&Some(ref new_url)), &Some(ref old_url)) => {
                        Some(format!("OK, I'll post this discussion to {} instead of {} like you said before",
                                     new_url,
                                     old_url))
                    }
                };

                if let Some(new_url) = new_url_option {
                    data.github_url = new_url;
                }

                if !line.is_action {
                    if line.message.starts_with("RESOLUTION") ||
                       line.message.starts_with("RESOLVED") {
                        data.resolutions.push(line.message.clone());
                    }

                    data.lines.push(line);
                }

                response
            }
        }
    }

    fn start_topic(&mut self, topic: &str) {
        self.end_topic();
        self.current_topic = Some(TopicData::new(topic));
    }

    fn end_topic(&mut self) {
        // TODO: Test the topic boundary code.
        if let Some(topic) = self.current_topic.take() {
            if topic.github_url.is_some() {
                let task = GithubCommentTask::new(topic, self.options);
                task.run();
            }
        }
    }
}

fn extract_github_url(message: &str, options: &HashMap<String, String>) -> Option<Option<String>> {
    lazy_static! {
        static ref GITHUB_URL_RE: Regex =
            Regex::new(r"^https://github.com/(?P<repo>[^/]*/[^/]*)/issues/(?P<number>[0-9]+)$")
            .unwrap();
    }
    let ref allowed_repos = options["github_repos_allowed"];
    if let Some(ref maybe_url) = strip_ci_prefix(&message, "github topic:") {
        if maybe_url.to_lowercase() == "none" {
            return Some(None);
        } else if let Some(ref caps) = GITHUB_URL_RE.captures(maybe_url) {
            for repo in allowed_repos.split_whitespace() {
                if caps["repo"] == *repo {
                    return Some(Some(maybe_url.clone()));
                }
            }
        }
    }
    None
}

struct GithubCommentTask {
    data: TopicData,
    github: Github,
}

impl GithubCommentTask {
    fn new(data_: TopicData, options: &HashMap<String, String>) -> GithubCommentTask {
        let github_ =
            Github::new(&*options["github_uastring"],
                        Client::with_connector(HttpsConnector::new(NativeTlsClient::new()
                                                                       .unwrap())),
                        Credentials::Token(options["github_access_token"].clone()));
        GithubCommentTask {
            data: data_,
            github: github_,
        }
    }
    fn run(self) {
        thread::spawn(move || { self.main(); });
    }
    fn main(&self) {
        lazy_static! {
            static ref GITHUB_URL_RE: Regex =
                Regex::new(r"^https://github.com/(?P<owner>[^/]*)/(?P<repo>[^/]*)/issues/(?P<number>[0-9]+)$")
                .unwrap();
        }

        if let Some(ref github_url) = self.data.github_url {
            if let Some(ref caps) = GITHUB_URL_RE.captures(github_url) {
                let repo = self.github
                    .repo(String::from(&caps["owner"]), String::from(&caps["repo"]));
                let issue = repo.issue(caps["number"].parse::<u64>().unwrap());
                let comments = issue.comments();

                let comment_text = format!("{}", self.data);
                comments
                    .create(&CommentOptions { body: comment_text })
                    .unwrap();
            } else {
                warn!("How does {} fail to match now when it matched before?",
                      github_url)
            }
        }
    }
}
<|endoftext|>


<commit_before>extern crate rustc_serialize;
use rustc_serialize::json;

use std::fmt;
use std::env;

use std::fs::File;
use std::io::Read;

use std::thread;
use std::sync::{Mutex, Arc};
use std::process::Command;

extern crate ansi_term;
use ansi_term::Colour;

struct WorkConf {
    project: String,
    operate: String,
    head:    String,
}

impl fmt::Debug for WorkConf {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "WorkConf:{}\"project\": {}, \"operate\": {}, \"head\": {}{}",
            "{", self.project, self.operate, self.head, "}")
    }
}

// Automatically generate `RustcDecodable` and `RustcEncodable` trait
// implementations
#[derive(RustcDecodable, RustcEncodable)]
struct DeployConf {
    user: String,
    path: String,
    hosts_conf: Vec<String>,
}

fn usage(argv_0: &String) {
    println!("\n{}", Colour::Cyan.bold().paint("-----USAGE----"));
    println!("{} {} {}             {}",
        Colour::Green.bold().paint(argv_0.to_string()),
        Colour::Blue.bold().paint("project"),
        Colour::Yellow.bold().paint("deploy"),
        Colour::Red.paint("deploy project with latest <head>."));
    println!("{} {} {} {}    {}\n",
        Colour::Green.bold().paint(argv_0.to_string()),
        Colour::Blue.bold().paint("project"),
        Colour::Yellow.bold().paint("rollback"),
        Colour::Purple.bold().paint("<head>"),
        Colour::Red.paint("rollback with <head>."));
    std::process::exit(0);
}

static DEBUG: bool = false;

fn main() {
    let args: Vec<_> = env::args().collect();
    let argc = args.len();

    if argc < 3 {
        usage(&args[0]);
    }

    // 处理命令行参数
    let project = args[1].to_string();
    let operate = args[2].to_string();
    if ("deploy".to_string() != operate && "rollback".to_string() != operate) || ("rollback".to_string() == operate && argc < 4) {
        usage(&args[0]);
    }

    let head = if argc > 3 { args[3].to_string() } else { "".to_string() };

    let work_conf = WorkConf{
        project: project,
        operate: operate,
        head:    head,
    };
    if DEBUG { println!("{:?}", work_conf); }

    let config_filename = format!("./conf/{}.json", work_conf.project);
    if DEBUG { println!("config_filename: {}", config_filename); }

    let mut data = String::new();
    let mut f = File::open(config_filename)
        .ok()
        .expect("Can NOT open file.");
    f.read_to_string(&mut data)
        .ok()
        .expect("Can NOT read cofing file.");
    if DEBUG { println!("deploy_conf: {}", data)}
    let deploy_conf: DeployConf = json::decode(&data)
        .ok()
        .expect("Can NOT parse JSON config data.");
    if DEBUG { println!("JSON: {}", json::encode(&deploy_conf).unwrap()); }

    let mut hosts_conf = Vec::new();
    let hosts_len = deploy_conf.hosts_conf.len();
    for index in 0 .. hosts_len {
        hosts_conf.push(deploy_conf.hosts_conf[index].clone());
    }
    let mutex_ct = Arc::new(Mutex::new(vec![1]));

    println!("          ===== {} ======\n", Colour::Blue.bold().paint(work_conf.project.to_string()));

    let handles: Vec<_> = hosts_conf.into_iter().map(|host| {
        let user = deploy_conf.user.clone();
        let path = deploy_conf.path.clone();
        let operate = work_conf.operate.clone();
        let head    = work_conf.head.clone();
        let mutex_ct= mutex_ct.clone();
        thread::spawn(move || {
            let cmd = if "deploy".to_string() == operate {
                format!("ssh {}@{} \"cd {} && git pull && git log -1 | awk '{{if (\\$1 ~/commit/) {{print \\$2}}}}' 2>&1\"",
                user, host, path)
            } else {
                format!("ssh {}@{} \"cd {} && git reset --hard {} && git log -1 | awk '{{if (\\$1 ~/commit/) {{print \\$2}}}}' 2>&1\"",
                user, host, path, head)
            };
            if DEBUG { println!("CMD: {}", cmd); }
            let output = Command::new("sh")
                .arg("-c")
                .arg(cmd)
                .output()
                .unwrap_or_else(|e| { panic!("failed to execute process: {}", e) });

            // 解决多线程穿插输出的问题
            let user_at_host = format!("{}{}{}",
                Colour::Green.bold().paint(user.to_string()),
                Colour::Red.bold().paint("@"),
                Colour::Cyan.bold().paint(host.to_string()));
            let mut output_buf = "".to_string();
            output_buf = output_buf + &(format!("--- {} {} for {} ---\n",
                Colour::Cyan.bold().paint("START"),
                Colour::Purple.bold().paint(operate.to_string()), user_at_host));
            if DEBUG { println!("status: {}", output.status); }
            output_buf = output_buf + &(format!("{} {}\n", Colour::Green.bold().paint("stdout:"), String::from_utf8_lossy(&output.stdout)));
            output_buf = output_buf + &(format!("{} {}\n", Colour::Red.bold().paint("stderr:"),String::from_utf8_lossy(&output.stderr)));
            output_buf = output_buf + &(format!("--- {} ---\n\n", Colour::Cyan.bold().paint("END")));

            {
                let _ct = mutex_ct.lock().unwrap();
                println!("{}", output_buf);
            }
        })
    }).collect();

    for h in handles {
        h.join().unwrap();
    }
}
<commit_msg>按返回码处理输出内容.<commit_after>extern crate rustc_serialize;
use rustc_serialize::json;

use std::fmt;
use std::env;

use std::fs::File;
use std::io::Read;

use std::thread;
use std::sync::{Mutex, Arc};
use std::process::Command;

extern crate ansi_term;
use ansi_term::Colour;

struct WorkConf {
    project: String,
    operate: String,
    head:    String,
}

impl fmt::Debug for WorkConf {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "WorkConf:{}\"project\": {}, \"operate\": {}, \"head\": {}{}",
            "{", self.project, self.operate, self.head, "}")
    }
}

// Automatically generate `RustcDecodable` and `RustcEncodable` trait
// implementations
#[derive(RustcDecodable, RustcEncodable)]
struct DeployConf {
    user: String,
    path: String,
    hosts_conf: Vec<String>,
}

fn usage(argv_0: &String) {
    println!("\n{}", Colour::Cyan.bold().paint("-----USAGE----"));
    println!("{} {} {}             {}",
        Colour::Green.bold().paint(argv_0.to_string()),
        Colour::Blue.bold().paint("project"),
        Colour::Yellow.bold().paint("deploy"),
        Colour::Red.paint("deploy project with latest <head>."));
    println!("{} {} {} {}    {}\n",
        Colour::Green.bold().paint(argv_0.to_string()),
        Colour::Blue.bold().paint("project"),
        Colour::Yellow.bold().paint("rollback"),
        Colour::Purple.bold().paint("<head>"),
        Colour::Red.paint("rollback with <head>."));
    std::process::exit(0);
}

static DEBUG: bool = false;

fn main() {
    let args: Vec<_> = env::args().collect();
    let argc = args.len();

    if argc < 3 {
        usage(&args[0]);
    }

    // 处理命令行参数
    let project = args[1].to_string();
    let operate = args[2].to_string();
    if ("deploy".to_string() != operate && "rollback".to_string() != operate) || ("rollback".to_string() == operate && argc < 4) {
        usage(&args[0]);
    }

    let head = if argc > 3 { args[3].to_string() } else { "".to_string() };

    let work_conf = WorkConf{
        project: project,
        operate: operate,
        head:    head,
    };
    if DEBUG { println!("{:?}", work_conf); }

    let config_filename = format!("./conf/{}.json", work_conf.project);
    if DEBUG { println!("config_filename: {}", config_filename); }

    let mut data = String::new();
    let mut f = File::open(config_filename)
        .ok()
        .expect("Can NOT open file.");
    f.read_to_string(&mut data)
        .ok()
        .expect("Can NOT read cofing file.");
    if DEBUG { println!("deploy_conf: {}", data)}
    let deploy_conf: DeployConf = json::decode(&data)
        .ok()
        .expect("Can NOT parse JSON config data.");
    if DEBUG { println!("JSON: {}", json::encode(&deploy_conf).unwrap()); }

    let mut hosts_conf = Vec::new();
    let hosts_len = deploy_conf.hosts_conf.len();
    for index in 0 .. hosts_len {
        hosts_conf.push(deploy_conf.hosts_conf[index].clone());
    }
    let mutex_ct = Arc::new(Mutex::new(vec![1]));

    println!("          ===== {} ======\n", Colour::Blue.bold().paint(work_conf.project.to_string()));

    let handles: Vec<_> = hosts_conf.into_iter().map(|host| {
        let user = deploy_conf.user.clone();
        let path = deploy_conf.path.clone();
        let operate = work_conf.operate.clone();
        let head    = work_conf.head.clone();
        let mutex_ct= mutex_ct.clone();
        thread::spawn(move || {
            let cmd = if "deploy".to_string() == operate {
                format!("ssh {}@{} \"cd {} && git pull && git log -1 | awk '{{if (\\$1 ~/commit/) {{print \\$2}}}}' 2>&1\"",
                user, host, path)
            } else {
                format!("ssh {}@{} \"cd {} && git reset --hard {} && git log -1 | awk '{{if (\\$1 ~/commit/) {{print \\$2}}}}' 2>&1\"",
                user, host, path, head)
            };
            if DEBUG { println!("CMD: {}", cmd); }
            let output = Command::new("sh")
                .arg("-c")
                .arg(cmd)
                .output()
                .unwrap_or_else(|e| { panic!("failed to execute process: {}", e) });

            let user_at_host = format!("{}{}{}",
                Colour::Green.bold().paint(user.to_string()),
                Colour::Red.bold().paint("@"),
                Colour::Cyan.bold().paint(host.to_string()));
            // 解决多线程穿插输出的问题
            let mut output_buf = "".to_string();
            output_buf = output_buf + &(format!("--- {} {} for {} ---\n",
                Colour::Cyan.bold().paint("START"),
                Colour::Purple.bold().paint(operate.to_string()), user_at_host));
            if Some(0) == output.status.code() {
                output_buf = output_buf + &(format!("{}\n", String::from_utf8_lossy(&output.stdout)));
                output_buf = output_buf + &(format!("{} {} for {}\n",
                    Colour::Purple.bold().paint(operate.to_string()),
                    Colour::Green.bold().paint("SUCCESS"),
                    user_at_host));
            } else {
                output_buf = output_buf + &(format!("{} {}\n", Colour::Red.bold().paint("stderr:"),String::from_utf8_lossy(&output.stderr)));
                output_buf = output_buf + &(format!("{} {} for {}\n",
                    Colour::Purple.bold().paint(operate.to_string()),
                    Colour::Red.bold().paint("FAIL!"),
                    user_at_host));
            }
            output_buf = output_buf + &(format!("--- {} ---\n\n", Colour::Cyan.bold().paint("END")));

            {
                let _ct = mutex_ct.lock().unwrap();
                if DEBUG { println!("status: {:?}", output.status.code()); }
                println!("{}", output_buf);
            }
        })
    }).collect();

    for h in handles {
        h.join().unwrap();
    }
}
<|endoftext|>


<commit_before>extern crate futures;
extern crate tokio_core;
extern crate serde_json;
#[macro_use]
extern crate serde_derive;

use std::io::{Result, Error, ErrorKind};

use futures::{Future, Sink, Stream, Poll, StartSend, AsyncSink, Async};
use tokio_core::reactor::Core;
use tokio_core::net::TcpListener;
use tokio_core::io::{Codec,EasyBuf,Io};
use serde_json::Value;
use serde_json::ser::to_vec;
use serde_json::de::from_slice;
use serde_json::value::{to_value, from_value};

// TODO: Modify the serialize/deserialize so it generates the right JSON

#[derive(Debug, Serialize, Deserialize)]
pub struct Request {
    pub method: String,
    pub params: Option<Value>,
    pub id: Value,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct RPCError {
    pub code: i64,
    pub message: String,
    pub data: Option<Value>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct Response {
    pub result: Option<Value>,
    pub error: Option<RPCError>,
    pub id: Value,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct Notification {
    pub method: String,
    pub params: Option<Value>,
}

#[derive(Debug, Serialize, Deserialize)]
pub enum Message {
    Request(Request),
    Response(Response),
    Notification(Notification),
    Batch(Vec<Message>),
    Unmatched(Value),
}

fn err_map(e: serde_json::error::Error) -> Error {
    Error::new(ErrorKind::Other, e)
}

struct JsonCodec;

impl Codec for JsonCodec {
    type In = Value;
    type Out = Value;
    fn decode(&mut self, buf: &mut EasyBuf) -> Result<Option<Value>> {
        // TODO: Use object boundary instead of newlines
        if let Some(i) = buf.as_slice().iter().position(|&b| b == b'\n') {
            let line = buf.drain_to(i);
            buf.drain_to(1);
            from_slice(line.as_slice()).map(Some).map_err(err_map)
        } else {
            Ok(None)
        }
    }
    fn encode(&mut self, msg: Value, buf: &mut Vec<u8>) -> Result<()> {
        *buf = to_vec(&msg).map_err(err_map)?;
        buf.push(b'\n');
        Ok(())
    }
}

pub struct RPCFramed<JsonStream>(JsonStream);

impl<JsonStream> RPCFramed<JsonStream> where
    JsonStream: Stream<Item = Value, Error = Error> + Sink<SinkItem = Value, SinkError = Error>
{
    pub fn new(stream: JsonStream) -> Self {
        RPCFramed(stream)
    }
}

impl<JsonStream> Sink for RPCFramed<JsonStream> where
    JsonStream: Stream<Item = Value, Error = Error> + Sink<SinkItem = Value, SinkError = Error>
{
    type SinkError = Error;
    type SinkItem = Message;
    fn start_send(&mut self, item: Message) -> StartSend<Message, Error> {
        let converted = to_value(&item).map_err(err_map)?;
        match self.0.start_send(converted) {
            Ok(AsyncSink::NotReady(_)) => Ok(AsyncSink::NotReady(item)),
            other => other.map(|_| AsyncSink::Ready),
        }
    }
    fn poll_complete(&mut self) -> Poll<(), Error> {
        self.0.poll_complete()
    }
}

// TODO: Implement some kind of error handling other than just bailing out?
// But that is going to be tricky with the batch where some of the items might error and some not.
impl<JsonStream> Stream for RPCFramed<JsonStream> where
    JsonStream: Stream<Item = Value, Error = Error> + Sink<SinkItem = Value, SinkError = Error>
{
    type Error = Error;
    type Item = Message;
    fn poll(&mut self) -> Poll<Option<Message>, Error> {
        match self.0.poll() {
            Err(e) => Err(e),
            Ok(Async::NotReady) => Ok(Async::NotReady),
            Ok(Async::Ready(None)) => Ok(Async::Ready(None)),
            Ok(Async::Ready(Some(value))) => {
                Ok(Async::Ready(Some(from_value(value).map_err(err_map)?)))
            },
        }
    }
}

fn main() {
    let mut core = Core::new().unwrap();
    let handle = core.handle();

    let listener = TcpListener::bind(&"127.0.0.1:2345".parse().unwrap(), &handle).unwrap();
    let connections = listener.incoming();
    let service = connections.for_each(|(stream, _)| {
        let jsonized = stream.framed(JsonCodec);
        let rpcized = RPCFramed::new(jsonized);
        let (w, r) = rpcized.split();
        let sent = w.send_all(r)
            .map(|_| ())
            .map_err(|_| {
                // TODO Something with the error ‒ logging?
                ()
            });
        // Do the sending in the background
        handle.spawn(sent);
        Ok(())
    });
    core.run(service).unwrap();
}
<commit_msg>Proper serialization of Messages<commit_after>extern crate futures;
extern crate tokio_core;
extern crate serde;
extern crate serde_json;
#[macro_use]
extern crate serde_derive;

use std::io::{Result as IoResult, Error, ErrorKind};

use futures::{Future, Sink, Stream, Poll, StartSend, AsyncSink, Async};
use futures::stream::{once, Once};
use tokio_core::reactor::Core;
use tokio_core::net::TcpListener;
use tokio_core::io::{Codec,EasyBuf,Io};
use serde::ser::{Serialize, Serializer, SerializeStruct};
use serde_json::Value;
use serde_json::ser::to_vec;
use serde_json::de::from_slice;
use serde_json::value::{to_value, from_value};

// TODO: Modify the serialize/deserialize so it generates the right JSON

#[derive(Debug, Deserialize)]
struct Version;

impl Serialize for Version {
    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
        serializer.serialize_str("2.0")
    }
}

#[derive(Debug, Serialize, Deserialize)]
pub struct Request {
    jsonrpc: Version,
    pub method: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub params: Option<Value>,
    pub id: Value,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct RPCError {
    pub code: i64,
    pub message: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub data: Option<Value>,
}

#[derive(Debug, Deserialize)]
pub struct Response {
    pub result: Result<Value, RPCError>,
    pub id: Value,
}

impl Serialize for Response {
    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
        let mut sub = serializer.serialize_struct("Response", 2)?;
        sub.serialize_field("id", &self.id)?;
        match self.result {
            Ok(ref value) => sub.serialize_field("result", value),
            Err(ref err) => sub.serialize_field("error", err),
        }?;
        sub.end()
    }
}

#[derive(Debug, Serialize, Deserialize)]
pub struct Notification {
    jsonrpc: Version,
    pub method: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub params: Option<Value>,
}

#[derive(Debug, Deserialize)]
pub enum Message {
    Request(Request),
    Response(Response),
    Notification(Notification),
    Batch(Vec<Message>),
    Unmatched(Value),
}

impl Serialize for Message {
    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
        match *self {
            Message::Request(ref req) => req.serialize(serializer),
            Message::Response(ref resp) => resp.serialize(serializer),
            Message::Notification(ref notif) => notif.serialize(serializer),
            Message::Batch(ref batch) => batch.serialize(serializer),
            Message::Unmatched(ref val) => val.serialize(serializer),
        }
    }
}

fn err_map(e: serde_json::error::Error) -> Error {
    Error::new(ErrorKind::Other, e)
}

struct JsonCodec;

impl Codec for JsonCodec {
    type In = Value;
    type Out = Value;
    fn decode(&mut self, buf: &mut EasyBuf) -> IoResult<Option<Value>> {
        // TODO: Use object boundary instead of newlines
        if let Some(i) = buf.as_slice().iter().position(|&b| b == b'\n') {
            let line = buf.drain_to(i);
            buf.drain_to(1);
            from_slice(line.as_slice()).map(Some).map_err(err_map)
        } else {
            Ok(None)
        }
    }
    fn encode(&mut self, msg: Value, buf: &mut Vec<u8>) -> IoResult<()> {
        *buf = to_vec(&msg).map_err(err_map)?;
        buf.push(b'\n');
        Ok(())
    }
}

pub struct RPCFramed<JsonStream>(JsonStream);

impl<JsonStream> RPCFramed<JsonStream> where
    JsonStream: Stream<Item = Value, Error = Error> + Sink<SinkItem = Value, SinkError = Error>
{
    pub fn new(stream: JsonStream) -> Self {
        RPCFramed(stream)
    }
}

impl<JsonStream> Sink for RPCFramed<JsonStream> where
    JsonStream: Stream<Item = Value, Error = Error> + Sink<SinkItem = Value, SinkError = Error>
{
    type SinkError = Error;
    type SinkItem = Message;
    fn start_send(&mut self, item: Message) -> StartSend<Message, Error> {
        let converted = to_value(&item).map_err(err_map)?;
        match self.0.start_send(converted) {
            Ok(AsyncSink::NotReady(_)) => Ok(AsyncSink::NotReady(item)),
            other => other.map(|_| AsyncSink::Ready),
        }
    }
    fn poll_complete(&mut self) -> Poll<(), Error> {
        self.0.poll_complete()
    }
}

// TODO: Implement some kind of error handling other than just bailing out?
// But that is going to be tricky with the batch where some of the items might error and some not.
impl<JsonStream> Stream for RPCFramed<JsonStream> where
    JsonStream: Stream<Item = Value, Error = Error> + Sink<SinkItem = Value, SinkError = Error>
{
    type Error = Error;
    type Item = Message;
    fn poll(&mut self) -> Poll<Option<Message>, Error> {
        match self.0.poll() {
            Err(e) => Err(e),
            Ok(Async::NotReady) => Ok(Async::NotReady),
            Ok(Async::Ready(None)) => Ok(Async::Ready(None)),
            Ok(Async::Ready(Some(value))) => {
                Ok(Async::Ready(Some(from_value(value).map_err(err_map)?)))
            },
        }
    }
}

fn main() {
    let mut core = Core::new().unwrap();
    let handle = core.handle();

    let listener = TcpListener::bind(&"127.0.0.1:2345".parse().unwrap(), &handle).unwrap();
    let connections = listener.incoming();
    let service = connections.for_each(|(stream, _)| {
        let jsonized = stream.framed(JsonCodec);
        let rpcized = RPCFramed::new(jsonized);
        let (w, r) = rpcized.split();
        let header: Once<_, Error> = once(Ok(Message::Batch(
            vec![
                Message::Request(Request { jsonrpc: Version, method: "Hello".to_owned(), params: Some(Value::Null), id: Value::Bool(true) }),
                Message::Response(Response { result: Ok(Value::Bool(false)), id: Value::Bool(true) }),
                Message::Response(Response { result: Err(RPCError { code: 42, message: "Wrong!".to_owned(), data: None }), id: Value::Null }),
                Message::Notification(Notification { jsonrpc: Version, method: "Alert!".to_owned(), params: Some(Value::String("blabla".to_owned())) }),
                Message::Unmatched(Value::String(String::new())),
            ])));
        let sent = w.send_all(header).and_then(|(sink, _)| sink.send_all(r))
            .map(|_| ())
            .map_err(|_| {
                // TODO Something with the error ‒ logging?
                ()
            });
        // Do the sending in the background
        handle.spawn(sent);
        Ok(())
    });
    core.run(service).unwrap();
}
<|endoftext|>


<commit_before>#![feature(io)]
#![feature(path)]
#![feature(collections)]
#![feature(core)]
extern crate docopt;
#[macro_use]
extern crate standard_error;

use docopt::Docopt;
use standard_error::StandardResult as Result;
use std::rc::Rc;

mod config;

static CONFIG_PATH: &'static str = "./partners.cfg";

// Write the Docopt usage string.
static USAGE: &'static str = "
Usage: partners info
       partners list
       partners add --name=<name> --nick=<nick> [--email=<email>]
       partners add

Options:
    -a, --archive  Copy everything.
";

#[derive(Debug)]
struct Config {
  domain: String,
  prefix: String,
  separator: String,
}

#[derive(Debug)]
struct Author {
  config: Rc<Config>,
  nick: String,
  name: String,
  email: String,
}

fn print_author_list(list: &[Author]) {
  for item in list.iter() {
    println!("{}:", item.nick);
    println!("  Name:  {}", item.name);
    println!("  Email: {}", item.email);
  }
}

fn parse_author_line(config: &Rc<Config>, line: &str) -> Result<Author> {
  let mut parts = line.splitn(1, ' ');
  let nick = parts.next().unwrap().split('.').nth(1).unwrap().to_string();
  let name = parts.next().unwrap().to_string();
  let email = match config::get(&format!("author.{}.email", nick)[]) {
    Ok(email) => email,
    Err(_) => format!("{}@{}", nick, try!(config::get("config.domain")))
  };

  Ok(Author { config: config.clone(), nick: nick, name: name, email: email })
}

fn get_authors(config: &Rc<Config>) -> Result<Vec<Author>> {
  let lines = try!(config::list("author.\\w+.name"));
  lines.iter().map(|line| parse_author_line(config, &line[])).collect()
}

fn write_author(author: &Author) -> Result<()> {
  try!(config::set(&*format!("author.{}.name", author.nick), &*author.name));
  try!(config::set(&*format!("author.{}.email", author.nick), &*author.email));
  Ok(())
}

fn main() {
  let args = Docopt::new(USAGE).and_then(|d| d.parse()).unwrap_or_else(|e| e.exit());

  let config = Rc::new(Config {
    domain: config::get("config.domain").unwrap_or_else(|_| "example.com".to_string()),
    prefix: config::get("config.prefix").unwrap_or_else(|_| "dev".to_string()),
    separator: config::get("config.separator").unwrap_or_else(|_| "+".to_string()),
  });

  if args.get_bool("list") {
    let authors = get_authors(&config).unwrap();
    print_author_list(&authors[]);
  } else if args.get_bool("add") {
    let author = Author {
      config: config.clone(),
      nick: args.get_str("--nick").to_string(),
      name: args.get_str("--name").to_string(),
      email: args.get_str("--email").to_string(),
    };
    write_author(&author).unwrap();
  }
}
<commit_msg>Make email optional<commit_after>#![feature(io)]
#![feature(path)]
#![feature(collections)]
#![feature(core)]
extern crate docopt;
#[macro_use]
extern crate standard_error;

use docopt::Docopt;
use standard_error::StandardResult as Result;
use std::rc::Rc;
use std::borrow::Cow;

mod config;

static CONFIG_PATH: &'static str = "./partners.cfg";

// Write the Docopt usage string.
static USAGE: &'static str = "
Usage: partners info
       partners list
       partners add --nick=<nick> --name=<name> [--email=<email>]
       partners add

Options:
    -a, --archive  Copy everything.
";

#[derive(Debug)]
struct Config {
  domain: String,
  prefix: String,
  separator: String,
}

#[derive(Debug)]
struct Author {
  config: Rc<Config>,
  nick: String,
  name: String,
  email: Option<String>,
}

impl Author {
  fn get_email(&self) -> std::string::CowString {
    match self.email {
      Some(ref email) => Cow::Borrowed(email.as_slice()),
      None => Cow::Owned(format!("{}@{}", self.nick, self.config.domain)),
    }
  }
}

fn print_author_list(list: &[Author]) {
  for item in list.iter() {
    println!("{}:", item.nick);
    println!("  Name:  {}", item.name);
    println!("  Email: {}", item.get_email());
  }
}

fn parse_author_line(config: &Rc<Config>, line: &str) -> Result<Author> {
  let mut parts = line.splitn(1, ' ');
  let nick = parts.next().unwrap().split('.').nth(1).unwrap().to_string();
  let name = parts.next().unwrap().to_string();
  let email = config::get(&format!("author.{}.email", nick)[]).ok();

  Ok(Author { config: config.clone(), nick: nick, name: name, email: email })
}

fn get_authors(config: &Rc<Config>) -> Result<Vec<Author>> {
  let lines = try!(config::list("author.\\w+.name"));
  lines.iter().map(|line| parse_author_line(config, &line[])).collect()
}

fn write_author(author: &Author) -> Result<()> {
  try!(config::set(&*format!("author.{}.name", author.nick), &*author.name));
  if let Some(ref email) = author.email {
    try!(config::set(&*format!("author.{}.email", author.nick), &**email));
  }
  Ok(())
}

fn main() {
  let args = Docopt::new(USAGE).and_then(|d| d.parse()).unwrap_or_else(|e| e.exit());

  let config = Rc::new(Config {
    domain: config::get("config.domain").unwrap_or_else(|_| "example.com".to_string()),
    prefix: config::get("config.prefix").unwrap_or_else(|_| "dev".to_string()),
    separator: config::get("config.separator").unwrap_or_else(|_| "+".to_string()),
  });

  if args.get_bool("list") {
    let authors = get_authors(&config).unwrap();
    print_author_list(&authors[]);
  } else if args.get_bool("add") {
    let email = args.get_str("--email").to_string();

    let email = if email.is_empty() { None } else { Some(email) };

    let author = Author {
      config: config.clone(),
      nick: args.get_str("--nick").to_string(),
      name: args.get_str("--name").to_string(),
      email: email,
    };
    write_author(&author).unwrap();
  }
}
<|endoftext|>


<commit_before>
extern crate rustc_serialize;
extern crate docopt;

use docopt::Docopt;

use std::thread;

use std::net::{TcpListener, TcpStream};
use std::net::SocketAddr;

use std::io::Read;
use std::str::FromStr;

const USAGE: &'static str = "
Naval Fate.

Usage:
  rust_redis [--port=<p>]
  naval_fate.py (-h | --help)
  naval_fate.py --version

Options:
  -h --help     Show this screen.
  --version     Show version.
  --port=<p>    Port to listen on [default: 5001]
";

#[derive(Debug, RustcDecodable)]
struct Args {
    flag_port: i32
}

fn handle_client(mut stream: TcpStream) {
    let mut buffer = Vec::<u8>::new();
    let data = stream.read_to_end(&mut buffer).unwrap();
    println!("{}", data);
}

fn main() {

    let args: Args = Docopt::new(USAGE)
        .and_then(|d| d.decode())
        .unwrap_or_else(|e| e.exit());

    println!("Listening on port: {:?}", args.flag_port);

    let addr: SocketAddr = SocketAddr::from_str(&format!("127.0.0.1:{}", args.flag_port)).unwrap();
    let listener = TcpListener::bind(addr).unwrap();

    for stream in listener.incoming() {
        match stream {
            Ok(stream) => {
                thread::spawn(move|| {
                    handle_client(stream)
                });
            }
            Err(e) => { println!("ERROR: {:}", e); }
        }
    }
}
<commit_msg>Echo text, add error handling<commit_after>
extern crate rustc_serialize;
extern crate docopt;

use docopt::Docopt;

use std::thread;

use std::net::{TcpListener, TcpStream};
use std::net::SocketAddr;

use std::io::Read;
use std::str::FromStr;

const USAGE: &'static str = "
Naval Fate.

Usage:
  rust_redis [--port=<p>]
  naval_fate.py (-h | --help)
  naval_fate.py --version

Options:
  -h --help     Show this screen.
  --version     Show version.
  --port=<p>    Port to listen on [default: 5001]
";

#[derive(Debug, RustcDecodable)]
struct Args {
    flag_port: i32
}

fn handle_client(mut stream: TcpStream) {
    let mut buffer = Vec::<u8>::new();
    let _ = stream.read_to_end(&mut buffer).unwrap();
    println!("{}", String::from_utf8(buffer).unwrap());
}

fn main() {

    let args: Args = Docopt::new(USAGE)
        .and_then(|d| d.decode())
        .unwrap_or_else(|e| e.exit());

    println!("Listening on port: {:?}", args.flag_port);

    let addr: SocketAddr = SocketAddr::from_str(&format!("127.0.0.1:{}", args.flag_port)).unwrap();
    let listener = TcpListener::bind(addr).unwrap();

    for stream in listener.incoming() {
        match stream {
            Ok(stream) => {
                thread::spawn(move|| {
                    handle_client(stream)
                });
            }
            Err(e) => {
                println!("ERROR: {:}", e);
            }
        }
    }
}
<|endoftext|>