<commit_before>extern crate evalbotlib as backend;
extern crate serde;
#[macro_use] extern crate serde_derive;
#[macro_use] extern crate log;
extern crate toml;
extern crate futures;
extern crate tokio;
extern crate telebot;
extern crate env_logger;

use backend::{EvalService, Language, util};

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

use futures::{Future, Stream, IntoFuture};
use telebot::RcBot;
use telebot::functions::*;
use telebot::objects::*;
use futures::future::Either;

macro_rules! nullify_future {
    ($task:expr, $fut:expr) => ($fut.map(|_| ())
        .or_else(|e| Ok(error!("error {}: {}", $task, e))));
}

static WHITELIST_FILENAME: &'static str = "tgwhitelist.toml";

#[derive(Clone, Serialize, Deserialize, PartialEq, Debug)]
struct TgCfg {
    owners: HashSet<String>,
    msg_owner_id: Option<i64>,
    bot_id: String,
    lang_subst: HashMap<String, String>
}

#[derive(Clone, Serialize, Deserialize, PartialEq, Debug)]
struct TgWhitelist {
    priv_enabled: bool,
    group_enabled: bool,
    allowed: HashSet<i64>,
    blocked: HashSet<i64>
}

impl TgWhitelist {
    fn priv_ok(&self, id: i64) -> bool {
        (!self.priv_enabled || self.allowed.contains(&id)) && !self.blocked.contains(&id)
    }

    fn group_ok(&self, id: i64) -> bool {
        (!self.group_enabled || self.allowed.contains(&id)) && !self.blocked.contains(&id)
    }

    fn allow(&mut self, id: i64) {
        self.allowed.insert(id);
    }

    fn unallow(&mut self, id: i64) {
        self.allowed.remove(&id);
    }

    fn block(&mut self, id: i64) {
        self.blocked.insert(id);
    }

    fn unblock(&mut self, id: i64) {
        self.blocked.remove(&id);
    }

    fn save<'a, 'b>(&'a self, path: &'static str) -> impl Future<Item = (), Error = ()> + 'b {
        nullify_future!("saving whitelist", util::encode(self, path))
    }
}

struct TgSvc {
    config: TgCfg,
    whitelist: RwLock<TgWhitelist>,
    service: EvalService,
}

fn telegram_wrap_result(s: &str) -> String {
    if s.is_empty() {
        "no output".to_owned()
    } else {
        let mut r = "
".to_owned();
        let input = s.as_bytes();
        r.push_str(&String::from_utf8_lossy(&input[..512.min(input.len())])
            .replace('\u{FFFD}', "")
            .replace('&', "&")
            .replace('<', "<")
            .replace('>', ">")
            .replace('"', """));
        r.push_str("
");
        if input.len() > 512 {
            r.push_str("... (truncated)");
        }
        r
    }
}

fn is_from_owner(msg: &Message, tgsvc: &TgSvc) -> bool {
    msg.from.as_ref().and_then(|u| u.username.as_ref().map(|un| tgsvc.is_owner(un))).unwrap_or(false)
}

fn handle_update((tgbot, update): (RcBot, Update), tgsvc: &Arc<TgSvc>) -> Result<(), ()> {
    if let Some((chat, Some(new_user))) = update.message.map(|m| (m.chat, m.new_chat_member)) {
        let chat_id = chat.id;
        if let (Ok(id), Ok(wl)) = (tgbot.inner.id.read(), tgsvc.whitelist.read()) {
            if *id == Some(new_user.id) && !wl.group_ok(chat_id) {
                tokio::spawn(nullify_future!("leaving group",
                    tgbot.message(chat_id, format!("You or this group is not on the whitelist. Seek help. ID: {}", chat_id)).send()
                        .and_then(move |(tgbot, _)| tgbot.leave_chat(chat_id).send())));
            }
        }
    }
    Ok(())
}

fn handle_eval(tgsvc: &Arc<TgSvc>, tgbot: RcBot, msg: Message, lang: &Arc<Language>, is_hash: bool)
    -> impl Future<Item = (), Error = ()> {
    let chat_id = msg.chat.id;
    if let Ok(wl) = tgsvc.whitelist.read() {
        if msg.chat.kind != "private" && !wl.group_ok(chat_id)
            || msg.chat.kind == "private" && !wl.priv_ok(chat_id) {
            tokio::spawn(nullify_future!("sending message",
                tgbot.message(chat_id, format!("You or this group is not on the whitelist. Seek help. ID: {}", chat_id)).send()));
            return Ok(()).into_future();
        }
    } else {
        error!("Failed to acquire RwLock");
        tokio::spawn(nullify_future!("sending message",
                tgbot.message(chat_id, "Internal error occurred".to_owned()).send()));
        return Ok(()).into_future();
    }

    let no_limit = is_hash && is_from_owner(&msg, tgsvc);
    let msg_id = msg.message_id;
    info!("({}) evaluating from {:?}: {:?}", msg_id, msg.from, msg.text.as_ref().map(|x| x.as_str()).unwrap_or(""));
    let code = msg.text.map(|x| x.trim().to_owned()).unwrap_or_else(|| "".to_owned());
    tokio::spawn(nullify_future!("sending message",
        lang.eval(code, if no_limit { Some(0) } else { None }, Some(format!("tg{}", chat_id)))
            .then(move |e| {
                info!("({}) result: {:?}", msg_id, e.as_ref().map(|x| x.as_str()).unwrap_or(""));
                tgbot.message(chat_id, telegram_wrap_result(&e.unwrap_or_else(|x| x)))
                    .parse_mode(ParseMode::HTML)
                    .reply_to_message_id(msg_id)
                    .send()
            })));

    Ok(()).into_future()
}

#[derive(Clone, Copy)]
enum WhitelistToggleOp {
    TogglePrivate,
    ToggleGroup
}

#[derive(Clone, Copy)]
enum WhitelistModOp {
    Allow,
    Unallow,
    Block,
    Unblock
}

fn handle_whitelist_toggle(me: &Arc<TgSvc>, tgbot: RcBot, msg: Message, op: WhitelistToggleOp)
    -> impl Future<Item = (), Error = ()> {
    if !is_from_owner(&msg, me) {
        return Either::A(Ok(()).into_future());
    }

    let resp = match me.whitelist.write() {
        Ok(mut wl) => match op {
            WhitelistToggleOp::TogglePrivate => {
                wl.priv_enabled = !wl.priv_enabled;
                tokio::spawn(wl.save(WHITELIST_FILENAME));
                format!("Private whitelist enabled: {}", wl.priv_enabled)
            }
            WhitelistToggleOp::ToggleGroup => {
                wl.group_enabled = !wl.group_enabled;
                tokio::spawn(wl.save(WHITELIST_FILENAME));
                format!("Group whitelist enabled: {}", wl.group_enabled)
            }
        }
        Err(err) => {
            error!("error while acquiring RwLock: {}", err);
            "error acquiring RwLock".to_owned()
        }
    };
    Either::B(nullify_future!("sending message", tgbot.message(msg.chat.id, resp)
        .reply_to_message_id(msg.message_id)
        .send()))
}

fn handle_whitelist_mod(me: &Arc<TgSvc>, tgbot: RcBot, msg: Message, op: WhitelistModOp)
    -> impl Future<Item = (), Error = ()> {
    if !is_from_owner(&msg, me) {
        return Either::A(Ok(()).into_future());
    }

    let arg = msg.text.as_ref().and_then(|t| t.trim().split_whitespace()
        .nth(0).and_then(|arg| arg.parse().ok()));
    let resp = match (arg, me.whitelist.write()) {
        (Some(id), Ok(mut wl)) => match op {
            WhitelistModOp::Allow => {
                wl.allow(id);
                tokio::spawn(wl.save(WHITELIST_FILENAME));
                format!("Allowed {}", id)
            }
            WhitelistModOp::Unallow => {
                wl.unallow(id);
                tokio::spawn(wl.save(WHITELIST_FILENAME));
                format!("Unallowed {}", id)
            }
            WhitelistModOp::Block => {
                wl.block(id);
                tokio::spawn(wl.save(WHITELIST_FILENAME));
                format!("Blocked {}", id)
            }
            WhitelistModOp::Unblock => {
                wl.unblock(id);
                tokio::spawn(wl.save(WHITELIST_FILENAME));
                format!("Unblocked {}", id)
            }
        }
        (None, _) => "Invalid ID".to_owned(),
        (_, Err(err)) => {
            error!("error while acquiring RwLock: {}", err);
            "error acquiring RwLock".to_owned()
        }
    };

    Either::B(nullify_future!("sending message", tgbot.message(msg.chat.id, resp)
        .reply_to_message_id(msg.message_id)
        .send()))
}

fn handle_leave(me: &Arc<TgSvc>, tgbot: RcBot, msg: Message)
    -> impl Future<Item = (), Error = ()> {
    if !is_from_owner(&msg, me) {
        return Either::A(Ok(()).into_future());
    }

    let arg = msg.text.as_ref().and_then(|t| t.trim().split_whitespace()
        .nth(0).and_then(|arg| arg.parse().ok()));
    let resp = match arg {
        Some(id) => {
            tokio::spawn(nullify_future!("leaving group", tgbot.leave_chat(id).send()));
            "OK"
        }
        None => "Invalid ID"
    }.to_owned();

    Either::B(nullify_future!("sending message", tgbot.message(msg.chat.id, resp)
        .reply_to_message_id(msg.message_id)
        .send()))
}

impl TgSvc {
    fn run() -> impl Future<Item = (), Error = ()> {
        let cfgf = util::decode::<TgCfg, _>("evalbot.tg.toml")
            .map(|cfg| {
                debug!("Loaded config: {:?}", cfg);
                cfg
            })
            .map_err(|e| {
                error!("failed to read evalbot.tg.toml: {}", e);
            });
        let wlf = util::decode::<TgWhitelist, _>(WHITELIST_FILENAME).or_else(|e| {
            warn!("failed to read whitelist: {}; using empty whitelist", e);
            Ok(TgWhitelist {
                priv_enabled: false,
                group_enabled: false,
                allowed: HashSet::new(),
                blocked: HashSet::new(),
            }).into_future()
        });
        cfgf.join(wlf).join(EvalService::from_toml_file("evalbot.toml")
            .map_err(|e| {
                error!("failed to read evalbot.toml: {}", e);
            }))
            .map(|((cfg, wl), es)| TgSvc {
                config: cfg,
                whitelist: RwLock::new(wl),
                service: es
            })
            .and_then(TgSvc::handle)
    }

    fn handle(self) -> impl Future<Item = (), Error = ()> {
        let bot = RcBot::new(&self.config.bot_id).expect("Failed to initialise Telegram bot");
        let me = Arc::new(self);
        bot.resolve_name();

        macro_rules! handle {
            ($cmd:expr, $handler:ident $(,$params:expr)*) => {{
                let me = me.clone();
                bot.register(bot.new_cmd($cmd)
                    .map_err(|e| error!("error in command processing: {}", e))
                    .and_then(move |(tgbot, msg)| $handler(&me, tgbot, msg $(,$params)*)));
            }};
        }

        for (name, lang) in me.service.langs() {
            {
                let lang = lang.clone();
                handle!(name, handle_eval, &lang, false);
            }
            {
                let lang = lang.clone();
                handle!(&format!("{}#", name), handle_eval, &lang, true);
            }
        }

        handle!("privwl", handle_whitelist_toggle, WhitelistToggleOp::TogglePrivate);
        handle!("groupwl", handle_whitelist_toggle, WhitelistToggleOp::ToggleGroup);
        handle!("allow", handle_whitelist_mod, WhitelistModOp::Allow);
        handle!("unallow", handle_whitelist_mod, WhitelistModOp::Unallow);
        handle!("block", handle_whitelist_mod, WhitelistModOp::Block);
        handle!("unblock", handle_whitelist_mod, WhitelistModOp::Unblock);
        handle!("leave", handle_leave);

        bot.get_stream()
            .map_err(|e| error!("{}", e))
            .for_each(move |tuple| handle_update(tuple, &me))
            .into_future()
    }

    fn is_owner(&self, name: &str) -> bool {
        self.config.owners.contains(name)
    }
}

fn main() {
    env_logger::init();
    tokio::run(TgSvc::run());
}
<commit_msg>tgbot: Truncate by lines in group chats<commit_after>extern crate evalbotlib as backend;
extern crate serde;
#[macro_use] extern crate serde_derive;
#[macro_use] extern crate log;
extern crate toml;
extern crate futures;
extern crate tokio;
extern crate telebot;
extern crate env_logger;

use backend::{EvalService, Language, util};

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

use futures::{Future, Stream, IntoFuture};
use telebot::RcBot;
use telebot::functions::*;
use telebot::objects::*;
use futures::future::Either;

macro_rules! nullify_future {
    ($task:expr, $fut:expr) => ($fut.map(|_| ())
        .or_else(|e| Ok(error!("error {}: {}", $task, e))));
}

static WHITELIST_FILENAME: &'static str = "tgwhitelist.toml";

#[derive(Clone, Serialize, Deserialize, PartialEq, Debug)]
struct TgCfg {
    owners: HashSet<String>,
    msg_owner_id: Option<i64>,
    bot_id: String,
    lang_subst: HashMap<String, String>
}

#[derive(Clone, Serialize, Deserialize, PartialEq, Debug)]
struct TgWhitelist {
    priv_enabled: bool,
    group_enabled: bool,
    allowed: HashSet<i64>,
    blocked: HashSet<i64>
}

impl TgWhitelist {
    fn priv_ok(&self, id: i64) -> bool {
        (!self.priv_enabled || self.allowed.contains(&id)) && !self.blocked.contains(&id)
    }

    fn group_ok(&self, id: i64) -> bool {
        (!self.group_enabled || self.allowed.contains(&id)) && !self.blocked.contains(&id)
    }

    fn allow(&mut self, id: i64) {
        self.allowed.insert(id);
    }

    fn unallow(&mut self, id: i64) {
        self.allowed.remove(&id);
    }

    fn block(&mut self, id: i64) {
        self.blocked.insert(id);
    }

    fn unblock(&mut self, id: i64) {
        self.blocked.remove(&id);
    }

    fn save<'a, 'b>(&'a self, path: &'static str) -> impl Future<Item = (), Error = ()> + 'b {
        nullify_future!("saving whitelist", util::encode(self, path))
    }
}

struct TgSvc {
    config: TgCfg,
    whitelist: RwLock<TgWhitelist>,
    service: EvalService,
}

fn telegram_wrap_result(s: &str, group: bool) -> String {
    if s.is_empty() {
        "no output".to_owned()
    } else {
        let mut r = "
".to_owned();
        let input = s.as_bytes();
        let mut cut_input = String::from_utf8_lossy(&input[..512.min(input.len())]);
        if group {
            cut_input = Cow::Owned(cut_input.lines().take(10).collect::<Vec<_>>().join("\n"));
        }
        r.push_str(&cut_input
            .replace(|c: char| c =='\u{FFFD}' || (c.is_control() && c != '\n' && c != '\t'), "")
            .replace('&', "&")
            .replace('<', "<")
            .replace('>', ">")
            .replace('"', """));
        r.push_str("
");
        if cut_input.len() + 1 // we also cut off the trailing \n
            < input.len() {
            r.push_str("... (truncated)");
        }
        r
    }
}

fn is_from_owner(msg: &Message, tgsvc: &TgSvc) -> bool {
    msg.from.as_ref().and_then(|u| u.username.as_ref().map(|un| tgsvc.is_owner(un))).unwrap_or(false)
}

fn handle_update((tgbot, update): (RcBot, Update), tgsvc: &Arc<TgSvc>) -> Result<(), ()> {
    if let Some((chat, Some(new_user))) = update.message.map(|m| (m.chat, m.new_chat_member)) {
        let chat_id = chat.id;
        if let (Ok(id), Ok(wl)) = (tgbot.inner.id.read(), tgsvc.whitelist.read()) {
            if *id == Some(new_user.id) && !wl.group_ok(chat_id) {
                tokio::spawn(nullify_future!("leaving group",
                    tgbot.message(chat_id, format!("You or this group is not on the whitelist. Seek help. ID: {}", chat_id)).send()
                        .and_then(move |(tgbot, _)| tgbot.leave_chat(chat_id).send())));
            }
        }
    }
    Ok(())
}

fn handle_eval(tgsvc: &Arc<TgSvc>, tgbot: RcBot, msg: Message, lang: &Arc<Language>, is_hash: bool)
    -> impl Future<Item = (), Error = ()> {
    let chat_id = msg.chat.id;
    let group = msg.chat.kind != "private";
    if let Ok(wl) = tgsvc.whitelist.read() {
        if group && !wl.group_ok(chat_id)
            || !group && !wl.priv_ok(chat_id) {
            tokio::spawn(nullify_future!("sending message",
                tgbot.message(chat_id, format!("You or this group is not on the whitelist. Seek help. ID: {}", chat_id)).send()));
            return Ok(()).into_future();
        }
    } else {
        error!("Failed to acquire RwLock");
        tokio::spawn(nullify_future!("sending message",
                tgbot.message(chat_id, "Internal error occurred".to_owned()).send()));
        return Ok(()).into_future();
    }

    let no_limit = is_hash && is_from_owner(&msg, tgsvc);
    let msg_id = msg.message_id;
    info!("({}) evaluating from {:?}: {:?}", msg_id, msg.from, msg.text.as_ref().map(|x| x.as_str()).unwrap_or(""));
    let code = msg.text.map(|x| {
        let mut r = x.trim_left().to_owned();
        r.push_str("\n");
        r
    }).unwrap_or_else(|| "".to_owned());
    tokio::spawn(nullify_future!("sending message",
        lang.eval(code, if no_limit { Some(0) } else { None }, Some(format!("tg{}", chat_id)))
            .then(move |e| {
                info!("({}) result: {:?}", msg_id, e.as_ref().map(|x| x.as_str()).unwrap_or(""));
                tgbot.message(chat_id, telegram_wrap_result(&e.unwrap_or_else(|x| x), group))
                    .parse_mode(ParseMode::HTML)
                    .reply_to_message_id(msg_id)
                    .send()
            })));

    Ok(()).into_future()
}

#[derive(Clone, Copy)]
enum WhitelistToggleOp {
    TogglePrivate,
    ToggleGroup
}

#[derive(Clone, Copy)]
enum WhitelistModOp {
    Allow,
    Unallow,
    Block,
    Unblock
}

fn handle_whitelist_toggle(me: &Arc<TgSvc>, tgbot: RcBot, msg: Message, op: WhitelistToggleOp)
    -> impl Future<Item = (), Error = ()> {
    if !is_from_owner(&msg, me) {
        return Either::A(Ok(()).into_future());
    }

    let resp = match me.whitelist.write() {
        Ok(mut wl) => match op {
            WhitelistToggleOp::TogglePrivate => {
                wl.priv_enabled = !wl.priv_enabled;
                tokio::spawn(wl.save(WHITELIST_FILENAME));
                format!("Private whitelist enabled: {}", wl.priv_enabled)
            }
            WhitelistToggleOp::ToggleGroup => {
                wl.group_enabled = !wl.group_enabled;
                tokio::spawn(wl.save(WHITELIST_FILENAME));
                format!("Group whitelist enabled: {}", wl.group_enabled)
            }
        }
        Err(err) => {
            error!("error while acquiring RwLock: {}", err);
            "error acquiring RwLock".to_owned()
        }
    };
    Either::B(nullify_future!("sending message", tgbot.message(msg.chat.id, resp)
        .reply_to_message_id(msg.message_id)
        .send()))
}

fn handle_whitelist_mod(me: &Arc<TgSvc>, tgbot: RcBot, msg: Message, op: WhitelistModOp)
    -> impl Future<Item = (), Error = ()> {
    if !is_from_owner(&msg, me) {
        return Either::A(Ok(()).into_future());
    }

    let arg = msg.text.as_ref().and_then(|t| t.trim().split_whitespace()
        .nth(0).and_then(|arg| arg.parse().ok()));
    let resp = match (arg, me.whitelist.write()) {
        (Some(id), Ok(mut wl)) => match op {
            WhitelistModOp::Allow => {
                wl.allow(id);
                tokio::spawn(wl.save(WHITELIST_FILENAME));
                format!("Allowed {}", id)
            }
            WhitelistModOp::Unallow => {
                wl.unallow(id);
                tokio::spawn(wl.save(WHITELIST_FILENAME));
                format!("Unallowed {}", id)
            }
            WhitelistModOp::Block => {
                wl.block(id);
                tokio::spawn(wl.save(WHITELIST_FILENAME));
                format!("Blocked {}", id)
            }
            WhitelistModOp::Unblock => {
                wl.unblock(id);
                tokio::spawn(wl.save(WHITELIST_FILENAME));
                format!("Unblocked {}", id)
            }
        }
        (None, _) => "Invalid ID".to_owned(),
        (_, Err(err)) => {
            error!("error while acquiring RwLock: {}", err);
            "error acquiring RwLock".to_owned()
        }
    };

    Either::B(nullify_future!("sending message", tgbot.message(msg.chat.id, resp)
        .reply_to_message_id(msg.message_id)
        .send()))
}

fn handle_leave(me: &Arc<TgSvc>, tgbot: RcBot, msg: Message)
    -> impl Future<Item = (), Error = ()> {
    if !is_from_owner(&msg, me) {
        return Either::A(Ok(()).into_future());
    }

    let arg = msg.text.as_ref().and_then(|t| t.trim().split_whitespace()
        .nth(0).and_then(|arg| arg.parse().ok()));
    let resp = match arg {
        Some(id) => {
            tokio::spawn(nullify_future!("leaving group", tgbot.leave_chat(id).send()));
            "OK"
        }
        None => "Invalid ID"
    }.to_owned();

    Either::B(nullify_future!("sending message", tgbot.message(msg.chat.id, resp)
        .reply_to_message_id(msg.message_id)
        .send()))
}

impl TgSvc {
    fn run() -> impl Future<Item = (), Error = ()> {
        let cfgf = util::decode::<TgCfg, _>("evalbot.tg.toml")
            .map(|cfg| {
                debug!("Loaded config: {:?}", cfg);
                cfg
            })
            .map_err(|e| {
                error!("failed to read evalbot.tg.toml: {}", e);
            });
        let wlf = util::decode::<TgWhitelist, _>(WHITELIST_FILENAME).or_else(|e| {
            warn!("failed to read whitelist: {}; using empty whitelist", e);
            Ok(TgWhitelist {
                priv_enabled: false,
                group_enabled: false,
                allowed: HashSet::new(),
                blocked: HashSet::new(),
            }).into_future()
        });
        cfgf.join(wlf).join(EvalService::from_toml_file("evalbot.toml")
            .map_err(|e| {
                error!("failed to read evalbot.toml: {}", e);
            }))
            .map(|((cfg, wl), es)| TgSvc {
                config: cfg,
                whitelist: RwLock::new(wl),
                service: es
            })
            .and_then(TgSvc::handle)
    }

    fn handle(self) -> impl Future<Item = (), Error = ()> {
        let bot = RcBot::new(&self.config.bot_id).expect("Failed to initialise Telegram bot");
        let me = Arc::new(self);
        bot.resolve_name();

        macro_rules! handle {
            ($cmd:expr, $handler:ident $(,$params:expr)*) => {{
                let me = me.clone();
                bot.register(bot.new_cmd($cmd)
                    .map_err(|e| error!("error in command processing: {}", e))
                    .and_then(move |(tgbot, msg)| $handler(&me, tgbot, msg $(,$params)*)));
            }};
        }

        for (name, lang) in me.service.langs() {
            {
                let lang = lang.clone();
                handle!(name, handle_eval, &lang, false);
            }
            {
                let lang = lang.clone();
                handle!(&format!("{}#", name), handle_eval, &lang, true);
            }
        }

        handle!("privwl", handle_whitelist_toggle, WhitelistToggleOp::TogglePrivate);
        handle!("groupwl", handle_whitelist_toggle, WhitelistToggleOp::ToggleGroup);
        handle!("allow", handle_whitelist_mod, WhitelistModOp::Allow);
        handle!("unallow", handle_whitelist_mod, WhitelistModOp::Unallow);
        handle!("block", handle_whitelist_mod, WhitelistModOp::Block);
        handle!("unblock", handle_whitelist_mod, WhitelistModOp::Unblock);
        handle!("leave", handle_leave);

        bot.get_stream()
            .map_err(|e| error!("{}", e))
            .for_each(move |tuple| handle_update(tuple, &me))
            .into_future()
    }

    fn is_owner(&self, name: &str) -> bool {
        self.config.owners.contains(name)
    }
}

fn main() {
    env_logger::init();
    tokio::run(TgSvc::run());
}
<|endoftext|>


<commit_before>#[macro_use(message)]
extern crate exonum;
#[macro_use]
extern crate log;

extern crate profiler;

use std::env;

use exonum::messages::{Message, RawTransaction, Error as MessageError};
use exonum::crypto::{Hash, hash};
use exonum::storage::{Error, View as StorageView};
use exonum::blockchain::{Service, Transaction};
use exonum::node::State;

pub const PROFILER_SERVICE: u16 = 7001;
pub const PROFILER_TRANSACTION_MESSAGE_ID: u16 = 7001;

const PROFILE_ENV_VARIABLE_NAME: &'static str = "PROFILE_FILENAME";

fn flame_dump() {
    use std::sync::{Once, ONCE_INIT};
    use profiler;
    use std::fs::File;

    static PROFILE_ONCE: Once = ONCE_INIT;

    PROFILE_ONCE.call_once(|| {
        if File::create(env::var(PROFILE_ENV_VARIABLE_NAME)
                              .unwrap_or("exonum-flame-graph.html".to_string()))
                 .and_then(|ref mut  file| profiler::dump_html(file) ).is_err() {
            warn!("FLAME_GRAPH, cant dump html!");
        }
    });

}

message! {
    ProfilerTx {
        const TYPE = PROFILER_SERVICE;
        const ID = PROFILER_TRANSACTION_MESSAGE_ID;
        const SIZE = 01;

        dump:      bool         [00 => 01]
    }
}

pub struct ProfilerService {}

impl ProfilerService {
    pub fn new() -> ProfilerService {
        ProfilerService {}
    }
}

impl Transaction for ProfilerTx {
    fn verify(&self) -> bool {
        true
    }

    fn execute(&self, _: &StorageView) -> Result<(), Error> {
        if self.dump() && cfg!(feature="flame_profile") {
            flame_dump();
        }
        Ok(())
    }

    fn raw(&self) -> &RawTransaction {
        Message::raw(self)
    }

    fn clone_box(&self) -> Box<Transaction> {
        Box::new(self.clone())
    }

    fn hash(&self) -> Hash {
        Message::hash(self)
    }
}

impl Service for ProfilerService {
    fn service_id(&self) -> u16 {
        PROFILER_SERVICE
    }

    fn handle_genesis_block(&self, _: &StorageView) -> Result<(), Error> {
        Ok(())
    }

    fn state_hash(&self, _: &StorageView) -> Result<Hash, Error> {
        Ok(hash(&[]))
    }

    fn tx_from_raw(&self, raw: RawTransaction) -> Result<Box<Transaction>, MessageError> {
        if raw.message_type() != PROFILER_TRANSACTION_MESSAGE_ID {
            return Err(MessageError::IncorrectMessageType { message_type: raw.message_type() });
        }

        ProfilerTx::from_raw(raw).map(|tx| Box::new(tx) as Box<Transaction>)
    }

    fn handle_commit(&self,
                     _: &StorageView,
                     _: &mut State)
                     -> Result<Vec<Box<Transaction>>, Error> {
        Ok(Vec::new())
    }
}<commit_msg>small fixes<commit_after>#[macro_use(message)]
extern crate exonum;
#[macro_use]
extern crate log;

extern crate profiler;

use std::env;

use exonum::messages::{Message, RawTransaction, Error as MessageError};
use exonum::crypto::{Hash, hash};
use exonum::storage::{Error, View as StorageView};
use exonum::blockchain::{Service, Transaction};
use exonum::node::State;

pub const PROFILER_SERVICE: u16 = 7001;
pub const PROFILER_TRANSACTION_MESSAGE_ID: u16 = 7002;

const PROFILE_ENV_VARIABLE_NAME: &'static str = "PROFILE_FILENAME";

fn flame_dump() {
    use profiler;
    use std::fs::File;

    if File::create(env::var(PROFILE_ENV_VARIABLE_NAME)
                          .unwrap_or("exonum-flame-graph.html".to_string()))
             .and_then(|ref mut  file| profiler::dump_html(file) ).is_err() {
        warn!("FLAME_GRAPH, cant dump html!");
    }

}

message! {
    ProfilerTx {
        const TYPE = PROFILER_SERVICE;
        const ID = PROFILER_TRANSACTION_MESSAGE_ID;
        const SIZE = 01;

        dump:      bool         [00 => 01]
    }
}

pub struct ProfilerService {}

impl ProfilerService {
    pub fn new() -> ProfilerService {
        ProfilerService {}
    }
}

impl Transaction for ProfilerTx {
    fn verify(&self) -> bool {
        true
    }

    fn execute(&self, _: &StorageView) -> Result<(), Error> {
        if self.dump() && cfg!(feature="flame_profile") {
            flame_dump();
        }
        Ok(())
    }

    fn raw(&self) -> &RawTransaction {
        Message::raw(self)
    }

    fn clone_box(&self) -> Box<Transaction> {
        Box::new(self.clone())
    }

    fn hash(&self) -> Hash {
        Message::hash(self)
    }
}

impl Service for ProfilerService {
    fn service_id(&self) -> u16 {
        PROFILER_SERVICE
    }

    fn handle_genesis_block(&self, _: &StorageView) -> Result<(), Error> {
        Ok(())
    }

    fn state_hash(&self, _: &StorageView) -> Result<Hash, Error> {
        Ok(hash(&[]))
    }

    fn tx_from_raw(&self, raw: RawTransaction) -> Result<Box<Transaction>, MessageError> {
        if raw.message_type() != PROFILER_TRANSACTION_MESSAGE_ID {
            return Err(MessageError::IncorrectMessageType { message_type: raw.message_type() });
        }

        ProfilerTx::from_raw(raw).map(|tx| Box::new(tx) as Box<Transaction>)
    }

    fn handle_commit(&self,
                     _: &StorageView,
                     _: &mut State)
                     -> Result<Vec<Box<Transaction>>, Error> {
        Ok(Vec::new())
    }
}<|endoftext|>


<commit_before>// Copyright 2020 Pants project contributors (see CONTRIBUTORS.md).
// Licensed under the Apache License, Version 2.0 (see LICENSE).

use std::io;
use std::net::Ipv4Addr;

use futures::channel::mpsc;
use futures::{try_join, SinkExt, Stream, StreamExt};
use nails::execution::{stream_for, ChildInput, ChildOutput, ExitCode};
use nails::Config;
use tokio::io::AsyncWriteExt;
use tokio::net::TcpStream;
use tokio::signal::unix::{signal, Signal, SignalKind};

pub enum NailgunClientError {
  PreConnect(String),
  PostConnect(String),
  BrokenPipe,
  KeyboardInterrupt,
}

fn handle_postconnect_stdio(err: io::Error, msg: &str) -> NailgunClientError {
  if err.kind() == io::ErrorKind::BrokenPipe {
    // A BrokenPipe error is a semi-expected error caused when stdout/stderr closes, and which
    // the Python runtime has a special error type and handling for.
    NailgunClientError::BrokenPipe
  } else {
    NailgunClientError::PostConnect(format!("{}: {}", msg, err))
  }
}

async fn handle_client_output(
  mut stdio_read: impl Stream<Item = ChildOutput> + Unpin,
  mut signal_stream: Signal,
  child: &mut nails::client::Child,
) -> Result<(), NailgunClientError> {
  let mut stdout = tokio::io::stdout();
  let mut stderr = tokio::io::stderr();
  let mut is_exiting = false;
  loop {
    tokio::select! {
      output = stdio_read.next() => {
        match output {
          Some(ChildOutput::Stdout(bytes)) => {
            stdout.write_all(&bytes).await.map_err(|err| handle_postconnect_stdio(err, "Failed to write to stdout"))?
          },
          Some(ChildOutput::Stderr(bytes)) => {
            stderr.write_all(&bytes).await.map_err(|err| handle_postconnect_stdio(err, "Failed to write to stderr"))?
          },
          None => break,
        }
      }
      _ = signal_stream.recv() => {
          if is_exiting {
              // This is the second signal: exit uncleanly to drop the child rather than waiting
              // further.
              return Err(NailgunClientError::KeyboardInterrupt);
          } else {
              // This is the first signal: trigger shutdown of the Child, which will request that
              // the server interrupt the run.
              child.shutdown().await;
              is_exiting = true;
          }
      }
    }
  }
  try_join!(stdout.flush(), stderr.flush())
    .map_err(|err| handle_postconnect_stdio(err, "Failed to flush stdio"))?;
  Ok(())
}

async fn handle_client_input(mut stdin_write: mpsc::Sender<ChildInput>) -> Result<(), io::Error> {
  use nails::execution::send_to_io;
  let mut stdin = stream_for(tokio::io::stdin());
  while let Some(input_bytes) = stdin.next().await {
    stdin_write
      .send(ChildInput::Stdin(input_bytes?))
      .await
      .map_err(send_to_io)?;
  }
  Ok(())
}

///
/// Execute the given command on the given localhost port.
///
/// This method installs (global!: see below) signal handling such that:
///   1. the first SIGINT will cause the client to attempt to exit gracefully.
///   2. the second SIGINT will eagerly exit the client without waiting for the server.
///
/// NB: This method installs a signal handler that will affect signal handling throughout the
/// entire process. Because of this, it should only be used in a process that is relatively
/// dedicated to the task of connecting to a nailgun server.
///
pub async fn client_execute(
  port: u16,
  command: String,
  args: Vec<String>,
  env: Vec<(String, String)>,
) -> Result<i32, NailgunClientError> {
  use nails::execution::{child_channel, Command};

  let working_dir =
    std::env::current_dir().map_err(|e| NailgunClientError::PreConnect(e.to_string()))?;

  let config = Config::default();
  let command = Command {
    command,
    args,
    env,
    working_dir,
  };

  let signal_stream = signal(SignalKind::interrupt()).map_err(|err| {
    NailgunClientError::PreConnect(format!("Failed to install interrupt handler: {}", err))
  })?;
  let socket = TcpStream::connect((Ipv4Addr::new(127, 0, 0, 1), port))
    .await
    .map_err(|err| {
      NailgunClientError::PreConnect(format!("Failed to connect to localhost: {}", err))
    })?;

  let mut child = nails::client::handle_connection(config, socket, command, async {
    let (stdin_write, stdin_read) = child_channel::<ChildInput>();
    let _input_handler = tokio::spawn(handle_client_input(stdin_write));
    stdin_read
  })
  .await
  .map_err(|err| NailgunClientError::PreConnect(format!("Failed to start: {}", err)))?;

  handle_client_output(
    child.output_stream.take().unwrap(),
    signal_stream,
    &mut child,
  )
  .await?;

  let exit_code: ExitCode = child.wait().await.map_err(|err| {
    let err_str = match err.to_string().as_str() {
      "Client exited before the server's result could be returned." => {
        "The pantsd process was killed during the run.\n\nIf this was not intentionally done by you, \
          Pants may have been killed by the operating system due to memory overconsumption \
          (i.e. OOM-killed). You can set the global option `--pantsd-max-memory-usage` to reduce \
          Pantsd's memory consumption by retaining less in its in-memory cache \
          (run `./pants help-advanced global`). You can also disable pantsd with the global \
          option `--no-pantsd` to avoid persisting memory across Pants runs, although you will miss \
          out on additional caching.\n\nIf neither of those help, please consider filing a \
          GitHub issue or reaching out on Slack so that we can investigate the possible memory \
          overconsumption (https://www.pantsbuild.org/docs/getting-help)."
          .to_owned()
      }
      _ => format!("Failed during execution: {}", err),
    };
    NailgunClientError::PostConnect(err_str)
  })?;

  Ok(exit_code.0)
}
<commit_msg>Revise the error message associated with OOM-killed events (#17373)<commit_after>// Copyright 2020 Pants project contributors (see CONTRIBUTORS.md).
// Licensed under the Apache License, Version 2.0 (see LICENSE).

use std::io;
use std::net::Ipv4Addr;

use futures::channel::mpsc;
use futures::{try_join, SinkExt, Stream, StreamExt};
use nails::execution::{stream_for, ChildInput, ChildOutput, ExitCode};
use nails::Config;
use tokio::io::AsyncWriteExt;
use tokio::net::TcpStream;
use tokio::signal::unix::{signal, Signal, SignalKind};

pub enum NailgunClientError {
  PreConnect(String),
  PostConnect(String),
  BrokenPipe,
  KeyboardInterrupt,
}

fn handle_postconnect_stdio(err: io::Error, msg: &str) -> NailgunClientError {
  if err.kind() == io::ErrorKind::BrokenPipe {
    // A BrokenPipe error is a semi-expected error caused when stdout/stderr closes, and which
    // the Python runtime has a special error type and handling for.
    NailgunClientError::BrokenPipe
  } else {
    NailgunClientError::PostConnect(format!("{}: {}", msg, err))
  }
}

async fn handle_client_output(
  mut stdio_read: impl Stream<Item = ChildOutput> + Unpin,
  mut signal_stream: Signal,
  child: &mut nails::client::Child,
) -> Result<(), NailgunClientError> {
  let mut stdout = tokio::io::stdout();
  let mut stderr = tokio::io::stderr();
  let mut is_exiting = false;
  loop {
    tokio::select! {
      output = stdio_read.next() => {
        match output {
          Some(ChildOutput::Stdout(bytes)) => {
            stdout.write_all(&bytes).await.map_err(|err| handle_postconnect_stdio(err, "Failed to write to stdout"))?
          },
          Some(ChildOutput::Stderr(bytes)) => {
            stderr.write_all(&bytes).await.map_err(|err| handle_postconnect_stdio(err, "Failed to write to stderr"))?
          },
          None => break,
        }
      }
      _ = signal_stream.recv() => {
          if is_exiting {
              // This is the second signal: exit uncleanly to drop the child rather than waiting
              // further.
              return Err(NailgunClientError::KeyboardInterrupt);
          } else {
              // This is the first signal: trigger shutdown of the Child, which will request that
              // the server interrupt the run.
              child.shutdown().await;
              is_exiting = true;
          }
      }
    }
  }
  try_join!(stdout.flush(), stderr.flush())
    .map_err(|err| handle_postconnect_stdio(err, "Failed to flush stdio"))?;
  Ok(())
}

async fn handle_client_input(mut stdin_write: mpsc::Sender<ChildInput>) -> Result<(), io::Error> {
  use nails::execution::send_to_io;
  let mut stdin = stream_for(tokio::io::stdin());
  while let Some(input_bytes) = stdin.next().await {
    stdin_write
      .send(ChildInput::Stdin(input_bytes?))
      .await
      .map_err(send_to_io)?;
  }
  Ok(())
}

///
/// Execute the given command on the given localhost port.
///
/// This method installs (global!: see below) signal handling such that:
///   1. the first SIGINT will cause the client to attempt to exit gracefully.
///   2. the second SIGINT will eagerly exit the client without waiting for the server.
///
/// NB: This method installs a signal handler that will affect signal handling throughout the
/// entire process. Because of this, it should only be used in a process that is relatively
/// dedicated to the task of connecting to a nailgun server.
///
pub async fn client_execute(
  port: u16,
  command: String,
  args: Vec<String>,
  env: Vec<(String, String)>,
) -> Result<i32, NailgunClientError> {
  use nails::execution::{child_channel, Command};

  let working_dir =
    std::env::current_dir().map_err(|e| NailgunClientError::PreConnect(e.to_string()))?;

  let config = Config::default();
  let command = Command {
    command,
    args,
    env,
    working_dir,
  };

  let signal_stream = signal(SignalKind::interrupt()).map_err(|err| {
    NailgunClientError::PreConnect(format!("Failed to install interrupt handler: {}", err))
  })?;
  let socket = TcpStream::connect((Ipv4Addr::new(127, 0, 0, 1), port))
    .await
    .map_err(|err| {
      NailgunClientError::PreConnect(format!("Failed to connect to localhost: {}", err))
    })?;

  let mut child = nails::client::handle_connection(config, socket, command, async {
    let (stdin_write, stdin_read) = child_channel::<ChildInput>();
    let _input_handler = tokio::spawn(handle_client_input(stdin_write));
    stdin_read
  })
  .await
  .map_err(|err| NailgunClientError::PreConnect(format!("Failed to start: {}", err)))?;

  handle_client_output(
    child.output_stream.take().unwrap(),
    signal_stream,
    &mut child,
  )
  .await?;

  let exit_code: ExitCode = child.wait().await.map_err(|err| {
    let err_str = match err.to_string().as_str() {
      "Client exited before the server's result could be returned." => {
        "The pantsd process was killed during the run.\n\nIf this was not intentionally done by you, \
          Pants may have been killed by the operating system due to memory overconsumption \
          (i.e. OOM-killed). If you keep seeing this error message, try the troubleshooting steps below. \
          If none of those help, please consider filing a GitHub issue or reaching out on Slack so that we can \
          investigate the possible memory overconsumption (https://www.pantsbuild.org/docs/getting-help).\n \
          - Exit other applications, including applications running in the background.\n \
          - Set the global option `--pantsd-max-memory-usage` to reduce Pantsd's memory consumption by retaining \
            less in its in-memory cache (run `./pants help-advanced global`).\n \
          - Disable pantsd with the global option `--no-pantsd` to avoid persisting memory across Pants runs, \
            although you will miss out on additional caching."
          .to_owned()
      }
      _ => format!("Failed during execution: {}", err),
    };
    NailgunClientError::PostConnect(err_str)
  })?;

  Ok(exit_code.0)
}
<|endoftext|>


<commit_before>use std::collections::HashSet;

use aster::AstBuilder;

use syntax::ast;
use syntax::ext::base::ExtCtxt;
use syntax::ptr::P;
use syntax::visit;

// Remove the default from every type parameter because in the generated impls
// they look like associated types: "error: associated type bindings are not
// allowed here".
pub fn without_defaults(generics: &ast::Generics) -> ast::Generics {
    ast::Generics {
        ty_params: generics.ty_params.map(|ty_param| {
            ast::TyParam {
                default: None,
                .. ty_param.clone()
            }}),
        .. generics.clone()
    }
}

pub fn with_bound(
    cx: &ExtCtxt,
    builder: &AstBuilder,
    item: &ast::Item,
    generics: &ast::Generics,
    filter: &Fn(&ast::StructField) -> bool,
    bound: &[&'static str],
) -> ast::Generics {
    let path = builder.path().global().ids(bound).build();

    builder.from_generics(generics.clone())
        .with_predicates(
            all_variants(cx, item).iter()
                .flat_map(|variant_data| all_struct_fields(variant_data))
                .filter(|field| filter(field))
                .map(|field| &field.ty)
                // TODO this filter can be removed later, see comment on function
                .filter(|ty| contains_generic(ty, generics))
                .map(|ty| strip_reference(ty))
                .map(|ty| builder.where_predicate()
                    // the type that is being bounded e.g. T
                    .bound().build(ty.clone())
                    // the bound e.g. Serialize
                    .bound().trait_(path.clone()).build()
                    .build()))
        .build()
}

fn all_variants<'a>(cx: &ExtCtxt, item: &'a ast::Item) -> Vec<&'a ast::VariantData> {
    match item.node {
        ast::ItemKind::Struct(ref variant_data, _) => {
            vec![variant_data]
        }
        ast::ItemKind::Enum(ref enum_def, _) => {
            enum_def.variants.iter()
                .map(|variant| &variant.node.data)
                .collect()
        }
        _ => {
            cx.span_bug(item.span, "expected Item to be Struct or Enum");
        }
    }
}

fn all_struct_fields(variant_data: &ast::VariantData) -> &[ast::StructField] {
    match *variant_data {
        ast::VariantData::Struct(ref fields, _) |
        ast::VariantData::Tuple(ref fields, _) => {
            fields
        }
        ast::VariantData::Unit(_) => {
            &[]
        }
    }
}

// Rust <1.7 enforces that `where` clauses involve generic type parameters. The
// corresponding compiler error is E0193. It is no longer enforced in Rust >=1.7
// so this filtering can be removed in the future when we stop supporting <1.7.
//
// E0193 means we must not generate a `where` clause like `i32: Serialize`
// because even though i32 implements Serialize, i32 is not a generic type
// parameter. Clauses like `T: Serialize` and `Option<T>: Serialize` are okay.
// This function decides whether a given type references any of the generic type
// parameters in the input `Generics`.
fn contains_generic(ty: &ast::Ty, generics: &ast::Generics) -> bool {
    struct FindGeneric<'a> {
        generic_names: &'a HashSet<ast::Name>,
        found_generic: bool,
    }
    impl<'a, 'v> visit::Visitor<'v> for FindGeneric<'a> {
        fn visit_path(&mut self, path: &'v ast::Path, _id: ast::NodeId) {
            if !path.global
                    && path.segments.len() == 1
                    && self.generic_names.contains(&path.segments[0].identifier.name) {
                self.found_generic = true;
            } else {
                visit::walk_path(self, path);
            }
        }
    }

    let generic_names: HashSet<_> = generics.ty_params.iter()
        .map(|ty_param| ty_param.ident.name)
        .collect();

    let mut visitor = FindGeneric {
        generic_names: &generic_names,
        found_generic: false,
    };
    visit::walk_ty(&mut visitor, ty);
    visitor.found_generic
}

// This is required to handle types that use both a reference and a value of
// the same type, as in:
//
//    enum Test<'a, T> where T: 'a {
//        Lifetime(&'a T),
//        NoLifetime(T),
//    }
//
// Preserving references, we would generate an impl like:
//
//    impl<'a, T> Serialize for Test<'a, T>
//        where &'a T: Serialize,
//              T: Serialize { ... }
//
// And taking a reference to one of the elements would fail with:
//
//    error: cannot infer an appropriate lifetime for pattern due
//    to conflicting requirements [E0495]
//        Test::NoLifetime(ref v) => { ... }
//                         ^~~~~
//
// Instead, we strip references before adding `T: Serialize` bounds in order to
// generate:
//
//    impl<'a, T> Serialize for Test<'a, T>
//        where T: Serialize { ... }
fn strip_reference(ty: &P<ast::Ty>) -> &P<ast::Ty> {
    match ty.node {
        ast::TyKind::Rptr(_, ref mut_ty) => &mut_ty.ty,
        _ => ty
    }
}
<commit_msg>Adapt for removal of old interfaces in libsyntax<commit_after>use std::collections::HashSet;

use aster::AstBuilder;

use syntax::ast;
use syntax::ext::base::ExtCtxt;
use syntax::ptr::P;
use syntax::visit;

// Remove the default from every type parameter because in the generated impls
// they look like associated types: "error: associated type bindings are not
// allowed here".
pub fn without_defaults(generics: &ast::Generics) -> ast::Generics {
    ast::Generics {
        ty_params: generics.ty_params.iter().map(|ty_param| {
            ast::TyParam {
                default: None,
                .. ty_param.clone()
            }}).collect(),
        .. generics.clone()
    }
}

pub fn with_bound(
    cx: &ExtCtxt,
    builder: &AstBuilder,
    item: &ast::Item,
    generics: &ast::Generics,
    filter: &Fn(&ast::StructField) -> bool,
    bound: &[&'static str],
) -> ast::Generics {
    let path = builder.path().global().ids(bound).build();

    builder.from_generics(generics.clone())
        .with_predicates(
            all_variants(cx, item).iter()
                .flat_map(|variant_data| all_struct_fields(variant_data))
                .filter(|field| filter(field))
                .map(|field| &field.ty)
                // TODO this filter can be removed later, see comment on function
                .filter(|ty| contains_generic(ty, generics))
                .map(|ty| strip_reference(ty))
                .map(|ty| builder.where_predicate()
                    // the type that is being bounded e.g. T
                    .bound().build(ty.clone())
                    // the bound e.g. Serialize
                    .bound().trait_(path.clone()).build()
                    .build()))
        .build()
}

fn all_variants<'a>(cx: &ExtCtxt, item: &'a ast::Item) -> Vec<&'a ast::VariantData> {
    match item.node {
        ast::ItemKind::Struct(ref variant_data, _) => {
            vec![variant_data]
        }
        ast::ItemKind::Enum(ref enum_def, _) => {
            enum_def.variants.iter()
                .map(|variant| &variant.node.data)
                .collect()
        }
        _ => {
            cx.span_bug(item.span, "expected Item to be Struct or Enum");
        }
    }
}

fn all_struct_fields(variant_data: &ast::VariantData) -> &[ast::StructField] {
    match *variant_data {
        ast::VariantData::Struct(ref fields, _) |
        ast::VariantData::Tuple(ref fields, _) => {
            fields
        }
        ast::VariantData::Unit(_) => {
            &[]
        }
    }
}

// Rust <1.7 enforces that `where` clauses involve generic type parameters. The
// corresponding compiler error is E0193. It is no longer enforced in Rust >=1.7
// so this filtering can be removed in the future when we stop supporting <1.7.
//
// E0193 means we must not generate a `where` clause like `i32: Serialize`
// because even though i32 implements Serialize, i32 is not a generic type
// parameter. Clauses like `T: Serialize` and `Option<T>: Serialize` are okay.
// This function decides whether a given type references any of the generic type
// parameters in the input `Generics`.
fn contains_generic(ty: &ast::Ty, generics: &ast::Generics) -> bool {
    struct FindGeneric<'a> {
        generic_names: &'a HashSet<ast::Name>,
        found_generic: bool,
    }
    impl<'a, 'v> visit::Visitor<'v> for FindGeneric<'a> {
        fn visit_path(&mut self, path: &'v ast::Path, _id: ast::NodeId) {
            if !path.global
                    && path.segments.len() == 1
                    && self.generic_names.contains(&path.segments[0].identifier.name) {
                self.found_generic = true;
            } else {
                visit::walk_path(self, path);
            }
        }
    }

    let generic_names: HashSet<_> = generics.ty_params.iter()
        .map(|ty_param| ty_param.ident.name)
        .collect();

    let mut visitor = FindGeneric {
        generic_names: &generic_names,
        found_generic: false,
    };
    visit::walk_ty(&mut visitor, ty);
    visitor.found_generic
}

// This is required to handle types that use both a reference and a value of
// the same type, as in:
//
//    enum Test<'a, T> where T: 'a {
//        Lifetime(&'a T),
//        NoLifetime(T),
//    }
//
// Preserving references, we would generate an impl like:
//
//    impl<'a, T> Serialize for Test<'a, T>
//        where &'a T: Serialize,
//              T: Serialize { ... }
//
// And taking a reference to one of the elements would fail with:
//
//    error: cannot infer an appropriate lifetime for pattern due
//    to conflicting requirements [E0495]
//        Test::NoLifetime(ref v) => { ... }
//                         ^~~~~
//
// Instead, we strip references before adding `T: Serialize` bounds in order to
// generate:
//
//    impl<'a, T> Serialize for Test<'a, T>
//        where T: Serialize { ... }
fn strip_reference(ty: &P<ast::Ty>) -> &P<ast::Ty> {
    match ty.node {
        ast::TyKind::Rptr(_, ref mut_ty) => &mut_ty.ty,
        _ => ty
    }
}
<|endoftext|>


<commit_before>use std::ops::{Mul, Add, Div, Sub, Index};

struct Matrix {
	cols: usize,
	rows: usize,
	data: Vec<f32>
}

impl Matrix {
    fn new(cols: usize, rows: usize, data: Vec<f32>) -> Matrix {
        Matrix {
            cols: cols,
            rows: rows,
            data: data
        }
    }

    fn zeros(cols: usize, rows: usize) -> Matrix {
    	Matrix {
            cols: cols,
            rows: rows,
            data: vec![0.0; cols*rows]
        }
    }

    fn identity(size: usize) -> Matrix {
    	let mut data = vec![0.0; size * size];

    	for i in 0..size
    	{
    		data[(i*(size+1)) as usize] = 1.0;
    	}

    	Matrix {
            cols: size,
            rows: size,
            data: data
        }
    }

    fn from_diag(diag: &Vec<f32>) -> Matrix {
    	let size = diag.len();
    	let mut data = vec![0.0; size * size];

    	for i in 0..size
    	{
    		data[(i*(size+1)) as usize] = diag[i];
    	}

    	Matrix {
            cols: size,
            rows: size,
            data: data
        }
    }
}

impl Mul<f32> for Matrix {
    type Output = Matrix;

    fn mul(self, f: f32) -> Matrix {
        let new_data = self.data.into_iter().map(|v| v * f).collect();

        Matrix {
            cols: self.cols,
            rows: self.rows,
            data: new_data
        }
    }
}

impl Mul<Matrix> for Matrix {
	type Output = Matrix;

	fn mul(self, m: Matrix) -> Matrix {
		// Will use Strassen algorithm if large, traditional otherwise
		return self;
	}
}

impl Add<f32> for Matrix {
	type Output = Matrix;

	fn add(self, f: f32) -> Matrix {
		let new_data = self.data.into_iter().map(|v| v + f).collect();

        Matrix {
            cols: self.cols,
            rows: self.rows,
            data: new_data
        }
    }
}

impl Sub<f32> for Matrix {
	type Output = Matrix;

	fn sub(self, f: f32) -> Matrix {
		let new_data = self.data.into_iter().map(|v| v - f).collect();

        Matrix {
            cols: self.cols,
            rows: self.rows,
            data: new_data
        }
    }
}

impl Add<Matrix> for Matrix {
	type Output = Matrix;

	fn add(self, m: Matrix) -> Matrix {
		assert!(self.cols == m.cols);
		assert!(self.rows == m.rows);

		let new_data = self.data.into_iter().enumerate().map(|(i,v)| v + m.data[i]).collect();

        Matrix {
            cols: self.cols,
            rows: self.rows,
            data: new_data
        }
	}
}

impl Sub<Matrix> for Matrix {
	type Output = Matrix;

	fn sub(self, m: Matrix) -> Matrix {
		assert!(self.cols == m.cols);
		assert!(self.rows == m.rows);

		let new_data = self.data.into_iter().enumerate().map(|(i,v)| v - m.data[i]).collect();

        Matrix {
            cols: self.cols,
            rows: self.rows,
            data: new_data
        }
	}
}

impl Div<f32> for Matrix {
	type Output = Matrix;

	fn div(self, f: f32) -> Matrix {
		assert!(f != 0.0)

		let new_data = self.data.into_iter().map(|v| v / f).collect();

        Matrix {
            cols: self.cols,
            rows: self.rows,
            data: new_data
        }
	}
}<commit_msg>Adding matrix indexing.<commit_after>use std::ops::{Mul, Add, Div, Sub, Index};

struct Matrix {
	cols: usize,
	rows: usize,
	data: Vec<f32>
}

impl Matrix {
    fn new(cols: usize, rows: usize, data: Vec<f32>) -> Matrix {
        Matrix {
            cols: cols,
            rows: rows,
            data: data
        }
    }

    fn zeros(cols: usize, rows: usize) -> Matrix {
    	Matrix {
            cols: cols,
            rows: rows,
            data: vec![0.0; cols*rows]
        }
    }

    fn identity(size: usize) -> Matrix {
    	let mut data = vec![0.0; size * size];

    	for i in 0..size
    	{
    		data[(i*(size+1)) as usize] = 1.0;
    	}

    	Matrix {
            cols: size,
            rows: size,
            data: data
        }
    }

    fn from_diag(diag: &Vec<f32>) -> Matrix {
    	let size = diag.len();
    	let mut data = vec![0.0; size * size];

    	for i in 0..size
    	{
    		data[(i*(size+1)) as usize] = diag[i];
    	}

    	Matrix {
            cols: size,
            rows: size,
            data: data
        }
    }
}

impl Mul<f32> for Matrix {
    type Output = Matrix;

    fn mul(self, f: f32) -> Matrix {
        let new_data = self.data.into_iter().map(|v| v * f).collect();

        Matrix {
            cols: self.cols,
            rows: self.rows,
            data: new_data
        }
    }
}

impl Mul<Matrix> for Matrix {
	type Output = Matrix;

	fn mul(self, m: Matrix) -> Matrix {
		// Will use Strassen algorithm if large, traditional otherwise
		return self;
	}
}

impl Add<f32> for Matrix {
	type Output = Matrix;

	fn add(self, f: f32) -> Matrix {
		let new_data = self.data.into_iter().map(|v| v + f).collect();

        Matrix {
            cols: self.cols,
            rows: self.rows,
            data: new_data
        }
    }
}

impl Sub<f32> for Matrix {
	type Output = Matrix;

	fn sub(self, f: f32) -> Matrix {
		let new_data = self.data.into_iter().map(|v| v - f).collect();

        Matrix {
            cols: self.cols,
            rows: self.rows,
            data: new_data
        }
    }
}

impl Add<Matrix> for Matrix {
	type Output = Matrix;

	fn add(self, m: Matrix) -> Matrix {
		assert!(self.cols == m.cols);
		assert!(self.rows == m.rows);

		let new_data = self.data.into_iter().enumerate().map(|(i,v)| v + m.data[i]).collect();

        Matrix {
            cols: self.cols,
            rows: self.rows,
            data: new_data
        }
	}
}

impl Sub<Matrix> for Matrix {
	type Output = Matrix;

	fn sub(self, m: Matrix) -> Matrix {
		assert!(self.cols == m.cols);
		assert!(self.rows == m.rows);

		let new_data = self.data.into_iter().enumerate().map(|(i,v)| v - m.data[i]).collect();

        Matrix {
            cols: self.cols,
            rows: self.rows,
            data: new_data
        }
	}
}

impl Div<f32> for Matrix {
	type Output = Matrix;

	fn div(self, f: f32) -> Matrix {
		assert!(f != 0.0);

		let new_data = self.data.into_iter().map(|v| v / f).collect();

        Matrix {
            cols: self.cols,
            rows: self.rows,
            data: new_data
        }
	}
}

impl Index<[usize; 2]> for Matrix {
	type Output = f32;

	fn index(&self, idx : [usize; 2]) -> &f32 {
		assert!(idx[0] < self.cols);
		assert!(idx[1] < self.rows);

		&self.data[idx[0] * self.cols + idx[1]]
	}
}<|endoftext|>


<commit_before>//! Log all requests in a format similar to Heroku's router, but with additional
//! information that we care about like User-Agent

use super::prelude::*;
use crate::util::request_header;

use conduit::{header, RequestExt, StatusCode};

use crate::middleware::normalize_path::OriginalPath;
use crate::middleware::response_timing::ResponseTime;
use std::cell::RefCell;
use std::fmt::{self, Display, Formatter};

const SLOW_REQUEST_THRESHOLD_MS: u64 = 1000;

// A thread local is used instead of a request extension to avoid the need to pass the request
// object everywhere in the codebase. When migrating to async this will need to be moved to an
// async-equivalent, as thread locals misbehave in async contexes.
thread_local! {
    static CUSTOM_METADATA: RefCell<Vec<(&'static str, String)>> = RefCell::new(Vec::new());
}

#[derive(Default)]
pub(super) struct LogRequests();

impl Middleware for LogRequests {
    fn before(&self, _: &mut dyn RequestExt) -> BeforeResult {
        // Remove any metadata set by the previous task before processing any new request.
        CUSTOM_METADATA.with(|metadata| metadata.borrow_mut().clear());

        Ok(())
    }

    fn after(&self, req: &mut dyn RequestExt, res: AfterResult) -> AfterResult {
        RequestLine::new(req, &res).log();

        res
    }
}

pub fn add_custom_metadata<V: Display>(key: &'static str, value: V) {
    CUSTOM_METADATA.with(|metadata| metadata.borrow_mut().push((key, value.to_string())));
    sentry::configure_scope(|scope| scope.set_extra(key, value.to_string().into()));
}

#[cfg(test)]
pub(crate) fn get_log_message(key: &'static str) -> String {
    CUSTOM_METADATA.with(|metadata| {
        for (k, v) in &*metadata.borrow() {
            if key == *k {
                return v.clone();
            }
        }
        panic!("expected log message for {} not found", key);
    })
}

struct RequestLine<'r> {
    req: &'r dyn RequestExt,
    res: &'r AfterResult,
}

impl<'a> RequestLine<'a> {
    fn new(request: &'a dyn RequestExt, response: &'a AfterResult) -> Self {
        RequestLine {
            req: request,
            res: response,
        }
    }

    fn log(&self) {
        println!("{self}");
    }
}

impl Display for RequestLine<'_> {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        let mut line = LogLine::new(f);

        let status = self.res.as_ref().map(|res| res.status());
        let status = status.unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);

        let at = if status.is_server_error() {
            "error"
        } else {
            "info"
        };

        line.add_field("at", at)?;
        line.add_field("method", self.req.method())?;
        line.add_quoted_field("path", FullPath(self.req))?;

        let is_download_endpoint = self.req.path().ends_with("/download");
        let is_download_redirect = is_download_endpoint && status.is_redirection();

        // The request_id is not logged for successful download requests
        if !is_download_redirect {
            line.add_field("request_id", request_header(self.req, "x-request-id"))?;
        }

        line.add_quoted_field("fwd", request_header(self.req, "x-real-ip"))?;

        let response_time = self.req.extensions().get::<ResponseTime>();
        if let Some(response_time) = response_time {
            line.add_field("service", response_time)?;
        }

        // The `status` is not logged for successful download requests
        if !is_download_redirect {
            line.add_field("status", status.as_str())?;
        }

        line.add_quoted_field("user_agent", request_header(self.req, header::USER_AGENT))?;

        CUSTOM_METADATA.with(|metadata| {
            for (key, value) in &*metadata.borrow() {
                line.add_quoted_field(key, value)?;
            }
            fmt::Result::Ok(())
        })?;

        if let Err(err) = self.res {
            line.add_quoted_field("error", err)?;
        }

        if let Some(response_time) = response_time {
            if response_time.as_millis() > SLOW_REQUEST_THRESHOLD_MS {
                line.add_marker("SLOW REQUEST")?;
            }
        }

        Ok(())
    }
}

struct FullPath<'a>(&'a dyn RequestExt);

impl<'a> Display for FullPath<'a> {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        let request = self.0;

        let original_path = request.extensions().get::<OriginalPath>();
        let path = original_path
            .map(|p| p.0.as_str())
            .unwrap_or_else(|| request.path());

        write!(f, "{}", path)?;

        if let Some(q_string) = request.query_string() {
            write!(f, "?{}", q_string)?;
        }
        Ok(())
    }
}

struct LogLine<'f, 'g> {
    f: &'f mut Formatter<'g>,
    first: bool,
}

impl<'f, 'g> LogLine<'f, 'g> {
    fn new(f: &'f mut Formatter<'g>) -> Self {
        Self { f, first: true }
    }

    fn add_field<K: Display, V: Display>(&mut self, key: K, value: V) -> fmt::Result {
        self.start_item()?;

        key.fmt(self.f)?;
        self.f.write_str("=")?;
        value.fmt(self.f)?;

        Ok(())
    }

    fn add_quoted_field<K: Display, V: Display>(&mut self, key: K, value: V) -> fmt::Result {
        self.start_item()?;

        key.fmt(self.f)?;
        self.f.write_str("=\"")?;
        value.fmt(self.f)?;
        self.f.write_str("\"")?;

        Ok(())
    }

    fn add_marker<M: Display>(&mut self, marker: M) -> fmt::Result {
        self.start_item()?;

        marker.fmt(self.f)?;

        Ok(())
    }

    fn start_item(&mut self) -> fmt::Result {
        if !self.first {
            self.f.write_str(" ")?;
        }
        self.first = false;
        Ok(())
    }
}
<commit_msg>middleware::log_request: Use `tracing` macros instead of `println!()`<commit_after>//! Log all requests in a format similar to Heroku's router, but with additional
//! information that we care about like User-Agent

use super::prelude::*;
use crate::util::request_header;

use conduit::{header, RequestExt, StatusCode};

use crate::middleware::normalize_path::OriginalPath;
use crate::middleware::response_timing::ResponseTime;
use std::cell::RefCell;
use std::fmt::{self, Display, Formatter};

const SLOW_REQUEST_THRESHOLD_MS: u64 = 1000;

// A thread local is used instead of a request extension to avoid the need to pass the request
// object everywhere in the codebase. When migrating to async this will need to be moved to an
// async-equivalent, as thread locals misbehave in async contexes.
thread_local! {
    static CUSTOM_METADATA: RefCell<Vec<(&'static str, String)>> = RefCell::new(Vec::new());
}

#[derive(Default)]
pub(super) struct LogRequests();

impl Middleware for LogRequests {
    fn before(&self, _: &mut dyn RequestExt) -> BeforeResult {
        // Remove any metadata set by the previous task before processing any new request.
        CUSTOM_METADATA.with(|metadata| metadata.borrow_mut().clear());

        Ok(())
    }

    fn after(&self, req: &mut dyn RequestExt, res: AfterResult) -> AfterResult {
        RequestLine::new(req, &res).log();

        res
    }
}

pub fn add_custom_metadata<V: Display>(key: &'static str, value: V) {
    CUSTOM_METADATA.with(|metadata| metadata.borrow_mut().push((key, value.to_string())));
    sentry::configure_scope(|scope| scope.set_extra(key, value.to_string().into()));
}

#[cfg(test)]
pub(crate) fn get_log_message(key: &'static str) -> String {
    CUSTOM_METADATA.with(|metadata| {
        for (k, v) in &*metadata.borrow() {
            if key == *k {
                return v.clone();
            }
        }
        panic!("expected log message for {} not found", key);
    })
}

struct RequestLine<'r> {
    req: &'r dyn RequestExt,
    res: &'r AfterResult,
    status: StatusCode,
}

impl<'a> RequestLine<'a> {
    fn new(request: &'a dyn RequestExt, response: &'a AfterResult) -> Self {
        let status = response.as_ref().map(|res| res.status());
        let status = status.unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);

        RequestLine {
            req: request,
            res: response,
            status,
        }
    }

    fn log(&self) {
        if self.status.is_server_error() {
            error!(target: "http", "{self}");
        } else {
            info!(target: "http", "{self}");
        };
    }
}

impl Display for RequestLine<'_> {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        let mut line = LogLine::new(f);

        line.add_field("method", self.req.method())?;
        line.add_quoted_field("path", FullPath(self.req))?;

        let is_download_endpoint = self.req.path().ends_with("/download");
        let is_download_redirect = is_download_endpoint && self.status.is_redirection();

        // The request_id is not logged for successful download requests
        if !is_download_redirect {
            line.add_field("request_id", request_header(self.req, "x-request-id"))?;
        }

        line.add_quoted_field("fwd", request_header(self.req, "x-real-ip"))?;

        let response_time = self.req.extensions().get::<ResponseTime>();
        if let Some(response_time) = response_time {
            line.add_field("service", response_time)?;
        }

        // The `status` is not logged for successful download requests
        if !is_download_redirect {
            line.add_field("status", self.status.as_str())?;
        }

        line.add_quoted_field("user_agent", request_header(self.req, header::USER_AGENT))?;

        CUSTOM_METADATA.with(|metadata| {
            for (key, value) in &*metadata.borrow() {
                line.add_quoted_field(key, value)?;
            }
            fmt::Result::Ok(())
        })?;

        if let Err(err) = self.res {
            line.add_quoted_field("error", err)?;
        }

        if let Some(response_time) = response_time {
            if response_time.as_millis() > SLOW_REQUEST_THRESHOLD_MS {
                line.add_marker("SLOW REQUEST")?;
            }
        }

        Ok(())
    }
}

struct FullPath<'a>(&'a dyn RequestExt);

impl<'a> Display for FullPath<'a> {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        let request = self.0;

        let original_path = request.extensions().get::<OriginalPath>();
        let path = original_path
            .map(|p| p.0.as_str())
            .unwrap_or_else(|| request.path());

        write!(f, "{}", path)?;

        if let Some(q_string) = request.query_string() {
            write!(f, "?{}", q_string)?;
        }
        Ok(())
    }
}

struct LogLine<'f, 'g> {
    f: &'f mut Formatter<'g>,
    first: bool,
}

impl<'f, 'g> LogLine<'f, 'g> {
    fn new(f: &'f mut Formatter<'g>) -> Self {
        Self { f, first: true }
    }

    fn add_field<K: Display, V: Display>(&mut self, key: K, value: V) -> fmt::Result {
        self.start_item()?;

        key.fmt(self.f)?;
        self.f.write_str("=")?;
        value.fmt(self.f)?;

        Ok(())
    }

    fn add_quoted_field<K: Display, V: Display>(&mut self, key: K, value: V) -> fmt::Result {
        self.start_item()?;

        key.fmt(self.f)?;
        self.f.write_str("=\"")?;
        value.fmt(self.f)?;
        self.f.write_str("\"")?;

        Ok(())
    }

    fn add_marker<M: Display>(&mut self, marker: M) -> fmt::Result {
        self.start_item()?;

        marker.fmt(self.f)?;

        Ok(())
    }

    fn start_item(&mut self) -> fmt::Result {
        if !self.first {
            self.f.write_str(" ")?;
        }
        self.first = false;
        Ok(())
    }
}
<|endoftext|>


<commit_before>use seed::prelude::*;
use crate::Msg;

pub mod mersenne_utils {
    extern crate num_bigint;
    extern crate num_traits;

    use num_traits::{Pow,Num};
    use num_bigint::{BigInt,ToBigInt};

    pub fn mersennes_discovery_dates(n:usize) -> String {
        let mersennes_discovery_dates:Vec<Vec<&str>> = vec![
            //TODO: Complete this list all the way upto 50
            //TODO: We could include download links etc
            vec![""],//faux zero entry to make things easier when reading this vector
            vec!["500BC"],
            vec!["500BC"],
            vec!["275BC"],
            vec!["275BC"],
            vec!["1456"],
            vec!["1588"],
            vec!["1588"],
            vec!["1772"],
            vec!["1883"],
            vec!["1911"],
            vec!["1914"],
            vec!["1876"],
            vec!["30-Jan-1952"],
            vec!["30-Jan-1952"],
            vec!["26-Jun-1952"],
            vec!["7-Oct-1952"],
            vec!["9-Oct-1952"],
            vec!["8-Sep-1957"],
            vec!["3-Nov-1961"],
            vec!["3-Nov-1961"],
            vec!["11-May-1963"],
            vec!["16-May-1963"],
            vec!["2-Jun-1963"],
            vec!["4-Mar-1971"],
            vec!["30-Oct-1978"],
            vec!["9-Feb-1979"],
            vec!["8-Apr-1979"],
            vec!["25-Sep-1982"],
            vec!["28-Jan-1988"],
            vec!["20-Sep-1983"],
            vec!["6-Sep-1985"],
            vec!["19-Feb-1992"],
            vec!["10-Jan-1994"],
            vec!["3-Sep-1996"],
            vec!["12-Nov-1996"],
            vec!["24-Aug-1997"],
            vec!["27-Jan-1998"],
            vec!["1-Jun-1999"],
            vec!["14-Nov-2001"],
            vec!["17-Nov-2003"],
            vec!["28-May-2004"],
            vec!["26-Feb-2005"],
            vec!["15-Dec-2005"],
            vec!["4-Sep-2006"],
            vec!["23-Aug-2008"],
            vec!["06-Sep-2009"],
            vec!["12-Apr-2008"],
            vec!["25-Jan-2013"],
            vec!["07-Jan-2016"],
            vec!["26-Dec-2017"],
        ];

        mersennes_discovery_dates[n][0].to_owned()
    }

    pub fn mersennes() -> Vec<Vec<usize>> {
        vec![
            //vec![p,digits]
            vec![0,     0],//faux zero entry to make things easier when reading this vector
            vec![2,     1],
            vec![3,     1],
            vec![5,     2],
            vec![7,     3],
            vec![13,    4],
            vec![17,    6],
            vec![19,    6],
            vec![31,   10],
            vec![61,   19],
            vec![89,   27],
            vec![107,  33],
            vec![127,  39],
            vec![521,  157],
            vec![607,  183],
            vec![1279, 386],
            vec![2203, 664],
            vec![2281, 687],
            vec![3217, 969],
            vec![4253,1281],
            vec![4423,1332],
            vec![9689,2917],
            vec![9941,2993],
            vec![11213,3376],
            vec![19937,6002],
            vec![21701,6533],
            vec![23209,6987],
            vec![44497,13395],
            vec![86243,25962],
            vec![110503,33265],
            vec![132049,39751],
            vec![216091,65050],
            vec![756839,227832],
            vec![859433,258716],
            vec![1257787,378632],
            vec![1398269,420921],
            vec![2976221,895832],
            vec![3021377,909526],
            vec![6972593,2098960],
            vec![13466917,4053946],
            vec![20996011,6320430],
            vec![24036583,7235733],
            vec![25964951,7816230],
            vec![30402457,9152052],
            vec![32582657,9808358],
            vec![37156667,11185272],
            vec![42643801,12837064],
            vec![43112609,12978189],
            vec![57885161,17425170],
            vec![74207281,22338618],
            vec![77232917,23249425],
        ]
    }

    pub fn nth_mersenne_prime (candidate:&str) -> u64 {
        let big_candidate:BigInt = num_bigint::BigInt::from_str_radix(&candidate, 10).unwrap();
        let mersennes:Vec<Vec<usize>> = mersennes();

        let mut answer:u64 = 0;
        let big_two:BigInt = 2.to_bigint().unwrap();
        for n in 1..mersennes.len() {
            let mprime:BigInt = big_two.pow(mersennes[n][0]) - 1;
            if big_candidate == mprime {
                answer = n as u64;
                break
            } else if big_candidate < mprime {
                break
            }
        }

        answer
    }
}

pub fn render() -> seed::dom_types::Node<Msg> {
    let mut html = vec![];

    let mersennes = mersenne_utils::mersennes();

    for n in 1..mersennes.len() {
        let download_txt:String = vec!["https://static.bigprimes.net/archive/mersenne/M",&n.to_string(),".txt"].into_iter().collect();
        let download_zip:String = vec!["https://static.bigprimes.net/archive/mersenne/M",&n.to_string(),".zip"].into_iter().collect();
        html.push(
            tr![
                td![n.to_string()],
                td!["2",sup![mersennes[n][0].to_string()],"-1"],
                td![mersennes[n][1].to_string()],
                td![mersenne_utils::mersennes_discovery_dates(n)],
                if n >= 30 {a![attrs!{At::Href => download_zip},"ZIP"]} else {a![attrs!{At::Href => download_txt},"TXT"]}
            ]
        );
    }

    html.reverse();

    div![
        h1!["The Mersenne Numbers"],
        br![],
        br![],
        br![],
        table![
            attrs!{At::Class => "mersennetable text"},
            tbody![
                tr![
                    td![
                        b!["No."]
                    ],
                    td![
                        b!["Prime"]
                    ],
                    td![
                        b!["Digits"]
                    ],
                    td![
                        b!["Discovered"]
                    ],
                    td![
                        b!["Download"]
                    ]
                ],
                html
            ]
        ]
    ]
}

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

    #[test]
    fn mersennes_discovery_dates_test() {
        assert_eq!(mersenne_utils::mersennes_discovery_dates(0), "");
        assert_eq!(mersenne_utils::mersennes_discovery_dates(50), "26-Dec-2017");
    }

    #[test]
    fn nth_mersenne_prime_test() {
        assert_eq!(mersenne_utils::nth_mersenne_prime("127"), 4);
        assert_eq!(mersenne_utils::nth_mersenne_prime("9"), 0);
    }
}<commit_msg>Added mersenne() unit tests<commit_after>use seed::prelude::*;
use crate::Msg;

pub mod mersenne_utils {
    extern crate num_bigint;
    extern crate num_traits;

    use num_traits::{Pow,Num};
    use num_bigint::{BigInt,ToBigInt};

    pub fn mersennes_discovery_dates(n:usize) -> String {
        let mersennes_discovery_dates:Vec<Vec<&str>> = vec![
            //TODO: Complete this list all the way upto 50
            //TODO: We could include download links etc
            vec![""],//faux zero entry to make things easier when reading this vector
            vec!["500BC"],
            vec!["500BC"],
            vec!["275BC"],
            vec!["275BC"],
            vec!["1456"],
            vec!["1588"],
            vec!["1588"],
            vec!["1772"],
            vec!["1883"],
            vec!["1911"],
            vec!["1914"],
            vec!["1876"],
            vec!["30-Jan-1952"],
            vec!["30-Jan-1952"],
            vec!["26-Jun-1952"],
            vec!["7-Oct-1952"],
            vec!["9-Oct-1952"],
            vec!["8-Sep-1957"],
            vec!["3-Nov-1961"],
            vec!["3-Nov-1961"],
            vec!["11-May-1963"],
            vec!["16-May-1963"],
            vec!["2-Jun-1963"],
            vec!["4-Mar-1971"],
            vec!["30-Oct-1978"],
            vec!["9-Feb-1979"],
            vec!["8-Apr-1979"],
            vec!["25-Sep-1982"],
            vec!["28-Jan-1988"],
            vec!["20-Sep-1983"],
            vec!["6-Sep-1985"],
            vec!["19-Feb-1992"],
            vec!["10-Jan-1994"],
            vec!["3-Sep-1996"],
            vec!["12-Nov-1996"],
            vec!["24-Aug-1997"],
            vec!["27-Jan-1998"],
            vec!["1-Jun-1999"],
            vec!["14-Nov-2001"],
            vec!["17-Nov-2003"],
            vec!["28-May-2004"],
            vec!["26-Feb-2005"],
            vec!["15-Dec-2005"],
            vec!["4-Sep-2006"],
            vec!["23-Aug-2008"],
            vec!["06-Sep-2009"],
            vec!["12-Apr-2008"],
            vec!["25-Jan-2013"],
            vec!["07-Jan-2016"],
            vec!["26-Dec-2017"],
        ];

        mersennes_discovery_dates[n][0].to_owned()
    }

    pub fn mersennes() -> Vec<Vec<usize>> {
        vec![
            //vec![p,digits]
            vec![0,     0],//faux zero entry to make things easier when reading this vector
            vec![2,     1],
            vec![3,     1],
            vec![5,     2],
            vec![7,     3],
            vec![13,    4],
            vec![17,    6],
            vec![19,    6],
            vec![31,   10],
            vec![61,   19],
            vec![89,   27],
            vec![107,  33],
            vec![127,  39],
            vec![521,  157],
            vec![607,  183],
            vec![1279, 386],
            vec![2203, 664],
            vec![2281, 687],
            vec![3217, 969],
            vec![4253,1281],
            vec![4423,1332],
            vec![9689,2917],
            vec![9941,2993],
            vec![11213,3376],
            vec![19937,6002],
            vec![21701,6533],
            vec![23209,6987],
            vec![44497,13395],
            vec![86243,25962],
            vec![110503,33265],
            vec![132049,39751],
            vec![216091,65050],
            vec![756839,227832],
            vec![859433,258716],
            vec![1257787,378632],
            vec![1398269,420921],
            vec![2976221,895832],
            vec![3021377,909526],
            vec![6972593,2098960],
            vec![13466917,4053946],
            vec![20996011,6320430],
            vec![24036583,7235733],
            vec![25964951,7816230],
            vec![30402457,9152052],
            vec![32582657,9808358],
            vec![37156667,11185272],
            vec![42643801,12837064],
            vec![43112609,12978189],
            vec![57885161,17425170],
            vec![74207281,22338618],
            vec![77232917,23249425],
        ]
    }

    pub fn nth_mersenne_prime (candidate:&str) -> u64 {
        let big_candidate:BigInt = num_bigint::BigInt::from_str_radix(&candidate, 10).unwrap();
        let mersennes:Vec<Vec<usize>> = mersennes();

        let mut answer:u64 = 0;
        let big_two:BigInt = 2.to_bigint().unwrap();
        for n in 1..mersennes.len() {
            let mprime:BigInt = big_two.pow(mersennes[n][0]) - 1;
            if big_candidate == mprime {
                answer = n as u64;
                break
            } else if big_candidate < mprime {
                break
            }
        }

        answer
    }
}

pub fn render() -> seed::dom_types::Node<Msg> {
    let mut html = vec![];

    let mersennes = mersenne_utils::mersennes();

    for n in 1..mersennes.len() {
        let download_txt:String = vec!["https://static.bigprimes.net/archive/mersenne/M",&n.to_string(),".txt"].into_iter().collect();
        let download_zip:String = vec!["https://static.bigprimes.net/archive/mersenne/M",&n.to_string(),".zip"].into_iter().collect();
        html.push(
            tr![
                td![n.to_string()],
                td!["2",sup![mersennes[n][0].to_string()],"-1"],
                td![mersennes[n][1].to_string()],
                td![mersenne_utils::mersennes_discovery_dates(n)],
                if n >= 30 {a![attrs!{At::Href => download_zip},"ZIP"]} else {a![attrs!{At::Href => download_txt},"TXT"]}
            ]
        );
    }

    html.reverse();

    div![
        h1!["The Mersenne Numbers"],
        br![],
        br![],
        br![],
        table![
            attrs!{At::Class => "mersennetable text"},
            tbody![
                tr![
                    td![
                        b!["No."]
                    ],
                    td![
                        b!["Prime"]
                    ],
                    td![
                        b!["Digits"]
                    ],
                    td![
                        b!["Discovered"]
                    ],
                    td![
                        b!["Download"]
                    ]
                ],
                html
            ]
        ]
    ]
}

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

    #[test]
    fn mersennes_discovery_dates_test() {
        assert_eq!(mersenne_utils::mersennes_discovery_dates(0), "");
        assert_eq!(mersenne_utils::mersennes_discovery_dates(50), "26-Dec-2017");
    }

    #[test]
    fn nth_mersenne_prime_test() {
        assert_eq!(mersenne_utils::nth_mersenne_prime("127"), 4);
        assert_eq!(mersenne_utils::nth_mersenne_prime("9"), 0);
    }

    #[test]
    fn mersenne_test() {
        let mersennes:Vec<Vec<usize>> = mersenne_utils::mersennes();
        assert_eq!(mersennes[0][0], 0);
        assert_eq!(mersennes[0][1], 0);

        assert_eq!(mersennes[2][0], 3);
        assert_eq!(mersennes[2][1], 1);
    }
}<|endoftext|>


<commit_before>// * This file is part of the uutils coreutils package.
// *
// * (c) 2015 Wiktor Kuropatwa <wiktor.kuropatwa@gmail.com>
// * (c) 2020 nicoo            <nicoo@debian.org>
// *
// * For the full copyright and license information, please view the LICENSE file
// * that was distributed with this source code.

use std::cmp::min;
use std::mem::swap;

pub fn gcd(mut u: u64, mut v: u64) -> u64 {
    // Stein's binary GCD algorithm
    // Base cases: gcd(n, 0) = gcd(0, n) = n
    if u == 0 {
        return v;
    } else if v == 0 {
        return u;
    }

    // gcd(2ⁱ u, 2ʲ v) = 2ᵏ gcd(u, v) with u, v odd and k = min(i, j)
    // 2ᵏ is the greatest power of two that divides both u and v
    let k = {
        let i = u.trailing_zeros();
        let j = v.trailing_zeros();
        u >>= i;
        v >>= j;
        min(i, j)
    };

    loop {
        // Loop invariant: u and v are odd
        debug_assert!(u % 2 == 1, "u = {} is even", u);
        debug_assert!(v % 2 == 1, "v = {} is even", v);

        // gcd(u, v) = gcd(|u - v|, min(u, v))
        if u > v {
            swap(&mut u, &mut v);
        }
        v -= u;

        if v == 0 {
            // Reached the base case; gcd is 2ᵏ u
            return u << k;
        }

        // gcd(u, 2ʲ v) = gcd(u, v) as u is odd
        v >>= v.trailing_zeros();
    }
}

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

    quickcheck! {
        fn euclidean(a: u64, b: u64) -> bool {
            // Test against the Euclidean algorithm
            let g = {
                let (mut a, mut b) = (a, b);
                while b > 0 {
                    a %= b;
                    swap(&mut a, &mut b);
                }
                a
            };
            gcd(a, b) == g
        }

        fn one(a: u64) -> bool {
            gcd(1, a) == 1
        }

        fn zero(a: u64) -> bool {
            gcd(0, a) == a
        }

        fn divisor(a: u64, b: u64) -> bool {
            // Test that gcd(a, b) divides a and b
            let g = gcd(a, b);
            if g != 0 {
                a % g == 0 && b % g == 0
            } else {
                a == 0 && b == 0 // for g == 0
            }
        }

        fn commutative(a: u64, b: u64) -> bool {
            gcd(a, b) == gcd(b, a)
        }

        fn associative(a: u64, b: u64, c: u64) -> bool {
            gcd(a, gcd(b, c)) == gcd(gcd(a, b), c)
        }

        fn scalar_mult(a: u64, b: u64, k: u64) -> bool {
            gcd(k * a, k * b) == k * gcd(a, b)
        }

        fn multiplicative(a: u64, b: u64, c: u64) -> bool {
            // gcd(ab, c) = gcd(a, c) gcd(b, c) when a and b coprime
            gcd(a, b) != 1 || gcd(a * b, c) == gcd(a, c) * gcd(b, c)
        }

        fn linearity(a: u64, b: u64, k: u64) -> bool {
            gcd(a + k * b, b) == gcd(a, b)
        }
    }
}
<commit_msg>factor::numeric::gcd: Refactor `divisor` test<commit_after>// * This file is part of the uutils coreutils package.
// *
// * (c) 2015 Wiktor Kuropatwa <wiktor.kuropatwa@gmail.com>
// * (c) 2020 nicoo            <nicoo@debian.org>
// *
// * For the full copyright and license information, please view the LICENSE file
// * that was distributed with this source code.

use std::cmp::min;
use std::mem::swap;

pub fn gcd(mut u: u64, mut v: u64) -> u64 {
    // Stein's binary GCD algorithm
    // Base cases: gcd(n, 0) = gcd(0, n) = n
    if u == 0 {
        return v;
    } else if v == 0 {
        return u;
    }

    // gcd(2ⁱ u, 2ʲ v) = 2ᵏ gcd(u, v) with u, v odd and k = min(i, j)
    // 2ᵏ is the greatest power of two that divides both u and v
    let k = {
        let i = u.trailing_zeros();
        let j = v.trailing_zeros();
        u >>= i;
        v >>= j;
        min(i, j)
    };

    loop {
        // Loop invariant: u and v are odd
        debug_assert!(u % 2 == 1, "u = {} is even", u);
        debug_assert!(v % 2 == 1, "v = {} is even", v);

        // gcd(u, v) = gcd(|u - v|, min(u, v))
        if u > v {
            swap(&mut u, &mut v);
        }
        v -= u;

        if v == 0 {
            // Reached the base case; gcd is 2ᵏ u
            return u << k;
        }

        // gcd(u, 2ʲ v) = gcd(u, v) as u is odd
        v >>= v.trailing_zeros();
    }
}

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

    quickcheck! {
        fn euclidean(a: u64, b: u64) -> bool {
            // Test against the Euclidean algorithm
            let g = {
                let (mut a, mut b) = (a, b);
                while b > 0 {
                    a %= b;
                    swap(&mut a, &mut b);
                }
                a
            };
            gcd(a, b) == g
        }

        fn one(a: u64) -> bool {
            gcd(1, a) == 1
        }

        fn zero(a: u64) -> bool {
            gcd(0, a) == a
        }

        fn divisor(a: u64, b: u64) -> () {
            // Test that gcd(a, b) divides a and b, unless a == b == 0
            if a == 0 && b == 0 { return; }

            let g = gcd(a, b);
            assert_eq!(a % g, 0);
            assert_eq!(b % g, 0);
        }

        fn commutative(a: u64, b: u64) -> bool {
            gcd(a, b) == gcd(b, a)
        }

        fn associative(a: u64, b: u64, c: u64) -> bool {
            gcd(a, gcd(b, c)) == gcd(gcd(a, b), c)
        }

        fn scalar_mult(a: u64, b: u64, k: u64) -> bool {
            gcd(k * a, k * b) == k * gcd(a, b)
        }

        fn multiplicative(a: u64, b: u64, c: u64) -> bool {
            // gcd(ab, c) = gcd(a, c) gcd(b, c) when a and b coprime
            gcd(a, b) != 1 || gcd(a * b, c) == gcd(a, c) * gcd(b, c)
        }

        fn linearity(a: u64, b: u64, k: u64) -> bool {
            gcd(a + k * b, b) == gcd(a, b)
        }
    }
}
<|endoftext|>


<commit_before>extern crate chrono;
extern crate strfmt;

use std::convert;
use std::collections::HashMap;
use self::strfmt::Format;
use status_checker::StatusDifference;
use models::Players;

#[derive(Debug)]
pub enum Error {
    FormatError(String),
}

impl convert::From<strfmt::FmtError> for Error {
    fn from(from: strfmt::FmtError) -> Error {
        Error::FormatError(from.to_string())
    }
}

pub struct StatusFormats {
    pub recover_msg: String,
    pub down_msg: String,
    pub join_fmt: String,
    pub leave_fmt: String,
    pub players_fmt: String,
    pub time_fmt: String,
}

impl StatusFormats {
    pub fn format(&self, status_difference: &StatusDifference) -> Result<Option<String>, Error> {
        use status_checker::StatusDifference::*;

        let mut buffer = String::with_capacity(560); // 140 chars * 4 bytes

        self.format_time(&mut buffer);

        // join / leave
        match status_difference {
            &PlayerChange { ref joined_players, ref left_players, .. } => {
                self.format_join(&mut buffer, joined_players)?;
                self.format_leave(&mut buffer, left_players)?;
            }
            &Recover { .. } => {
                buffer.push_str(&self.recover_msg);
                buffer.push('\n');
            },
            &Down { .. } => {
                buffer.push_str(&self.down_msg);
            },
            &None { } => {
                return Ok(Option::None);
                // do nothing
            },
        }

        // current players
        match status_difference {
            &PlayerChange { online_count, ref current_players, .. } |
            &Recover      { online_count, ref current_players, .. } => {
                self.format_current_players(&mut buffer, online_count, current_players)?;
            },
            _ => { },
        };

        Ok(Some(buffer))
    }

    fn format_time(&self, buffer: &mut String) {
        if ! self.time_fmt.is_empty() {
            let current_time = chrono::Local::now();
            let formatted_time = current_time.format(&self.time_fmt);
            buffer.push_str(&formatted_time.to_string());
            buffer.push('\n');
        }
    }

    fn format_join(&self, buffer: &mut String, players: &Players) -> Result<(), Error> {
        if ! players.is_empty() {
            Self::build_players(buffer, &self.join_fmt, players)?;
            buffer.push('\n');
        }
        Ok(())
    }

    fn format_leave(&self, buffer: &mut String, players: &Players) -> Result<(), Error> {
        if ! players.is_empty() {
            Self::build_players(buffer, &self.leave_fmt, players)?;
            buffer.push('\n');
        }
        Ok(())
    }

    fn format_current_players(&self, buffer: &mut String, online_count: u32, players: &Players) -> Result<(), Error> {
        let mut hashmap = HashMap::new();
        hashmap.insert("count".to_owned(), online_count.to_string());
        Self::build_players_hashmap(buffer, &mut hashmap, &self.players_fmt, players)
    }

    fn build_players(buffer: &mut String, fmt: &str, players: &Players) -> Result<(), Error> {
        let mut hashmap = HashMap::new();
        Self::build_players_hashmap(buffer, &mut hashmap, fmt, players)
    }

    fn build_players_hashmap(buffer: &mut String,
                             hashmap: &mut HashMap<String, String>,
                             fmt: &str,
                             players: &Players) -> Result<(), Error>
    {
        hashmap.insert("players".to_owned(), format!("{}", players));
        buffer.push_str(&fmt.format(&hashmap)?);
        Ok(())
    }
}

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

    fn setup_format() -> StatusFormats {
        let format = StatusFormats {
            recover_msg: "recovered".to_owned(),
            down_msg:    "down".to_owned(),
            join_fmt:    "{players}".to_owned(),
            leave_fmt:   "{players}".to_owned(),
            players_fmt: "{players} {count}".to_owned(),
            time_fmt:    "[]".to_owned(),
        };

        format
    }

    #[test]
    fn status_format_player_change() {
        let format = setup_format();

        let recover = StatusDifference::PlayerChange {
            online_count: 3,
            current_players: Players::from(vec![
                Player::new("idA", "A"),
                Player::new("idB", "B"),
                Player::new("idC", "C"),
            ]),
            joined_players: Players::from(vec![
                Player::new("idA", "A"),
                Player::new("idB", "B"),
            ]),
            left_players: Players::from(vec![
                Player::new("idD", "D"),
            ]),
        };

        assert_eq!(&format.format(&recover).unwrap(), "[]\nA, B\nD\nA, B, C 3");
    }

    #[test]
    fn status_format_recover() {
        let format = setup_format();

        let message = StatusDifference::Recover {
            online_count: 3,
            current_players: Players::from(vec![
                Player::new("idA", "A"),
                Player::new("idB", "B"),
                Player::new("idC", "C"),
            ]),
        };

        assert_eq!(&format.format(&message).unwrap(), "[]\nrecovered\nA, B, C 3");
    }

    #[test]
    fn status_format_down() {
        let format = setup_format();

        let message = StatusDifference::Down { reason: String::from("hoge") };
        assert_eq!(&format.format(&message).unwrap(), "[]\ndown");
    }
}
<commit_msg>fixed bug<commit_after>extern crate chrono;
extern crate strfmt;

use std::convert;
use std::collections::HashMap;
use self::strfmt::Format;
use status_checker::StatusDifference;
use models::Players;

#[derive(Debug)]
pub enum Error {
    FormatError(String),
}

impl convert::From<strfmt::FmtError> for Error {
    fn from(from: strfmt::FmtError) -> Error {
        Error::FormatError(from.to_string())
    }
}

pub struct StatusFormats {
    pub recover_msg: String,
    pub down_msg: String,
    pub join_fmt: String,
    pub leave_fmt: String,
    pub players_fmt: String,
    pub time_fmt: String,
}

impl StatusFormats {
    pub fn format(&self, status_difference: &StatusDifference) -> Result<Option<String>, Error> {
        use status_checker::StatusDifference::*;

        let mut buffer = String::with_capacity(560); // 140 chars * 4 bytes

        self.format_time(&mut buffer);

        // join / leave
        match status_difference {
            &PlayerChange { ref joined_players, ref left_players, .. } => {
                self.format_join(&mut buffer, joined_players)?;
                self.format_leave(&mut buffer, left_players)?;
            }
            &Recover { .. } => {
                buffer.push_str(&self.recover_msg);
                buffer.push('\n');
            },
            &Down { .. } => {
                buffer.push_str(&self.down_msg);
            },
            &None { } => {
                return Ok(Option::None);
                // do nothing
            },
        }

        // current players
        match status_difference {
            &PlayerChange { online_count, ref current_players, .. } |
            &Recover      { online_count, ref current_players, .. } => {
                self.format_current_players(&mut buffer, online_count, current_players)?;
            },
            _ => { },
        };

        Ok(Some(buffer))
    }

    fn format_time(&self, buffer: &mut String) {
        if ! self.time_fmt.is_empty() {
            let current_time = chrono::Local::now();
            let formatted_time = current_time.format(&self.time_fmt);
            buffer.push_str(&formatted_time.to_string());
            buffer.push('\n');
        }
    }

    fn format_join(&self, buffer: &mut String, players: &Players) -> Result<(), Error> {
        if ! players.is_empty() {
            Self::build_players(buffer, &self.join_fmt, players)?;
            buffer.push('\n');
        }
        Ok(())
    }

    fn format_leave(&self, buffer: &mut String, players: &Players) -> Result<(), Error> {
        if ! players.is_empty() {
            Self::build_players(buffer, &self.leave_fmt, players)?;
            buffer.push('\n');
        }
        Ok(())
    }

    fn format_current_players(&self, buffer: &mut String, online_count: u32, players: &Players) -> Result<(), Error> {
        let mut hashmap = HashMap::new();
        hashmap.insert("count".to_owned(), online_count.to_string());
        Self::build_players_hashmap(buffer, &mut hashmap, &self.players_fmt, players)
    }

    fn build_players(buffer: &mut String, fmt: &str, players: &Players) -> Result<(), Error> {
        let mut hashmap = HashMap::new();
        Self::build_players_hashmap(buffer, &mut hashmap, fmt, players)
    }

    fn build_players_hashmap(buffer: &mut String,
                             hashmap: &mut HashMap<String, String>,
                             fmt: &str,
                             players: &Players) -> Result<(), Error>
    {
        hashmap.insert("players".to_owned(), format!("{}", players));
        buffer.push_str(&fmt.format(&hashmap)?);
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use models::Player;

    fn setup_format() -> StatusFormats {
        let format = StatusFormats {
            recover_msg: "recovered".to_owned(),
            down_msg:    "down".to_owned(),
            join_fmt:    "{players}".to_owned(),
            leave_fmt:   "{players}".to_owned(),
            players_fmt: "{players} {count}".to_owned(),
            time_fmt:    "[]".to_owned(),
        };

        format
    }

    #[test]
    fn status_format_player_change() {
        let format = setup_format();

        let recover = StatusDifference::PlayerChange {
            online_count: 3,
            current_players: Players::from(vec![
                Player::new("idA", "A"),
                Player::new("idB", "B"),
                Player::new("idC", "C"),
            ]),
            joined_players: Players::from(vec![
                Player::new("idA", "A"),
                Player::new("idB", "B"),
            ]),
            left_players: Players::from(vec![
                Player::new("idD", "D"),
            ]),
        };

        assert_eq!(&format.format(&recover).unwrap().unwrap(), "[]\nA, B\nD\nA, B, C 3");
    }

    #[test]
    fn status_format_recover() {
        let format = setup_format();

        let message = StatusDifference::Recover {
            online_count: 3,
            current_players: Players::from(vec![
                Player::new("idA", "A"),
                Player::new("idB", "B"),
                Player::new("idC", "C"),
            ]),
        };

        assert_eq!(&format.format(&message).unwrap().unwrap(), "[]\nrecovered\nA, B, C 3");
    }

    #[test]
    fn status_format_down() {
        let format = setup_format();

        let message = StatusDifference::Down { reason: String::from("hoge") };
        assert_eq!(&format.format(&message).unwrap().unwrap(), "[]\ndown");
    }
}
<|endoftext|>