<commit_before>#![cfg(not(feature="no-stdlib"))]
use core::mem;
use std;
use core::marker::PhantomData;
use std::thread::{
    JoinHandle,
};
use alloc::{SliceWrapper, Allocator};
use enc::BrotliAlloc;
use enc::BrotliEncoderParams;
use enc::threading::{
  CompressMulti,
  SendAlloc,
  InternalSendAlloc,
  BatchSpawnable,
  Joinable,
  Owned,
  OwnedRetriever,
  CompressionThreadResult,
  InternalOwned,
  BrotliEncoderThreadError,
  AnyBoxConstructor,
  PoisonedThreadError,
  ReadGuard,
};

use std::sync::RwLock;

pub struct MultiThreadedJoinable<T:Send+'static, U:Send+'static>(JoinHandle<T>, PhantomData<U>);

impl<T:Send+'static, U:Send+'static+AnyBoxConstructor> Joinable<T, U> for MultiThreadedJoinable<T, U> {
  fn join(self) -> Result<T, U> {
      match self.0.join() {
          Ok(t) => Ok(t),
          Err(e) => Err(<U as AnyBoxConstructor>::new(e)),
      }
  }
}
pub struct MultiThreadedOwnedRetriever<U:Send+'static>(RwLock<U>);

impl<U:Send+'static> OwnedRetriever<U> for MultiThreadedOwnedRetriever<U> {
  fn view(&self) -> Result<ReadGuard<U>, PoisonedThreadError> {
      match self.0.read() {
          Ok(u) => Ok(ReadGuard::<U>(u)),
          Err(_) => Err(PoisonedThreadError::default()),
      }
  }
  fn unwrap(self) -> Result<U, PoisonedThreadError> {
      match self.0.into_inner() {
          Ok(u) => Ok(u),
          Err(_) => Err(PoisonedThreadError::default()),
      }
  }
}

#[derive(Default)]
pub struct MultiThreadedSpawner{}

fn spawn_work<T:Send+'static, F: Fn(usize, usize, &U, Alloc) -> T+Send+'static, Alloc:BrotliAlloc+Send+'static, U:Send+'static+Sync>(index: usize, num_threads: usize, locked_input:std::sync::Arc<RwLock<U>>, alloc:Alloc, f:F) -> std::thread::JoinHandle<T>
where <Alloc as Allocator<u8>>::AllocatedMemory:Send+'static {
  std::thread::spawn(move || {
    let guard = locked_input.view().unwrap();
    f(index, num_threads, &*guard, alloc)
  })
}

impl<T:Send+'static, Alloc:BrotliAlloc+Send+'static, U:Send+'static+Sync> BatchSpawnable<T, Alloc, U> for MultiThreadedSpawner
where <Alloc as Allocator<u8>>::AllocatedMemory:Send+'static {
  type JoinHandle = MultiThreadedJoinable<T, BrotliEncoderThreadError>;
  type FinalJoinHandle = std::sync::Arc<RwLock<U>>;
    fn batch_spawn<F: Fn(usize, usize, &U, Alloc) -> T+Send+'static+Copy>(
    &mut self,
    input: &mut Owned<U>,
    alloc_per_thread:&mut [SendAlloc<T, Alloc, Self::JoinHandle>],
    f: F,
    ) -> Self::FinalJoinHandle {
      let num_threads = alloc_per_thread.len();
      let locked_input = std::sync::Arc::<RwLock<U>>::new(RwLock::new(mem::replace(input, Owned(InternalOwned::Borrowed)).unwrap()));
      for (index, work) in alloc_per_thread.iter_mut().enumerate() {
        let alloc = work.replace_with_default();
        let ret = spawn_work(index, num_threads, locked_input.clone(), alloc, f.clone());
        *work = SendAlloc(InternalSendAlloc::Join(MultiThreadedJoinable(ret, PhantomData::default())));
      }
      locked_input
    }
}

pub fn compress_multi<Alloc:BrotliAlloc+Send+'static,
                      SliceW: SliceWrapper<u8>+Send+'static+Sync> (
  params:&BrotliEncoderParams,
  owned_input: &mut Owned<SliceW>,
  output: &mut [u8],
  alloc_per_thread:&mut [SendAlloc<CompressionThreadResult<Alloc>,
                                   Alloc,
                                   <MultiThreadedSpawner as BatchSpawnable<CompressionThreadResult<Alloc>,Alloc, SliceW>>::JoinHandle>],
) -> Result<usize, BrotliEncoderThreadError> where <Alloc as Allocator<u8>>::AllocatedMemory: Send {
  CompressMulti(params, owned_input, output, alloc_per_thread, MultiThreadedSpawner::default())
}
<commit_msg>work around ICE by using copy instead of clone<commit_after>#![cfg(not(feature="no-stdlib"))]
use core::mem;
use std;
use core::marker::PhantomData;
use std::thread::{
    JoinHandle,
};
use alloc::{SliceWrapper, Allocator};
use enc::BrotliAlloc;
use enc::BrotliEncoderParams;
use enc::threading::{
  CompressMulti,
  SendAlloc,
  InternalSendAlloc,
  BatchSpawnable,
  Joinable,
  Owned,
  OwnedRetriever,
  CompressionThreadResult,
  InternalOwned,
  BrotliEncoderThreadError,
  AnyBoxConstructor,
  PoisonedThreadError,
  ReadGuard,
};

use std::sync::RwLock;

pub struct MultiThreadedJoinable<T:Send+'static, U:Send+'static>(JoinHandle<T>, PhantomData<U>);

impl<T:Send+'static, U:Send+'static+AnyBoxConstructor> Joinable<T, U> for MultiThreadedJoinable<T, U> {
  fn join(self) -> Result<T, U> {
      match self.0.join() {
          Ok(t) => Ok(t),
          Err(e) => Err(<U as AnyBoxConstructor>::new(e)),
      }
  }
}
pub struct MultiThreadedOwnedRetriever<U:Send+'static>(RwLock<U>);

impl<U:Send+'static> OwnedRetriever<U> for MultiThreadedOwnedRetriever<U> {
  fn view(&self) -> Result<ReadGuard<U>, PoisonedThreadError> {
      match self.0.read() {
          Ok(u) => Ok(ReadGuard::<U>(u)),
          Err(_) => Err(PoisonedThreadError::default()),
      }
  }
  fn unwrap(self) -> Result<U, PoisonedThreadError> {
      match self.0.into_inner() {
          Ok(u) => Ok(u),
          Err(_) => Err(PoisonedThreadError::default()),
      }
  }
}

#[derive(Default)]
pub struct MultiThreadedSpawner{}

fn spawn_work<T:Send+'static, F: Fn(usize, usize, &U, Alloc) -> T+Send+'static, Alloc:BrotliAlloc+Send+'static, U:Send+'static+Sync>(index: usize, num_threads: usize, locked_input:std::sync::Arc<RwLock<U>>, alloc:Alloc, f:F) -> std::thread::JoinHandle<T>
where <Alloc as Allocator<u8>>::AllocatedMemory:Send+'static {
  std::thread::spawn(move || {
    let guard = locked_input.view().unwrap();
    f(index, num_threads, &*guard, alloc)
  })
}

impl<T:Send+'static, Alloc:BrotliAlloc+Send+'static, U:Send+'static+Sync> BatchSpawnable<T, Alloc, U> for MultiThreadedSpawner
where <Alloc as Allocator<u8>>::AllocatedMemory:Send+'static {
  type JoinHandle = MultiThreadedJoinable<T, BrotliEncoderThreadError>;
  type FinalJoinHandle = std::sync::Arc<RwLock<U>>;
    fn batch_spawn<F: Fn(usize, usize, &U, Alloc) -> T+Send+'static+Copy>(
    &mut self,
    input: &mut Owned<U>,
    alloc_per_thread:&mut [SendAlloc<T, Alloc, Self::JoinHandle>],
    f: F,
    ) -> Self::FinalJoinHandle {
      let num_threads = alloc_per_thread.len();
      let locked_input = std::sync::Arc::<RwLock<U>>::new(RwLock::new(mem::replace(input, Owned(InternalOwned::Borrowed)).unwrap()));
      for (index, work) in alloc_per_thread.iter_mut().enumerate() {
        let alloc = work.replace_with_default();
        let ret = spawn_work(index, num_threads, locked_input.clone(), alloc, f);
        *work = SendAlloc(InternalSendAlloc::Join(MultiThreadedJoinable(ret, PhantomData::default())));
      }
      locked_input
    }
}

pub fn compress_multi<Alloc:BrotliAlloc+Send+'static,
                      SliceW: SliceWrapper<u8>+Send+'static+Sync> (
  params:&BrotliEncoderParams,
  owned_input: &mut Owned<SliceW>,
  output: &mut [u8],
  alloc_per_thread:&mut [SendAlloc<CompressionThreadResult<Alloc>,
                                   Alloc,
                                   <MultiThreadedSpawner as BatchSpawnable<CompressionThreadResult<Alloc>,Alloc, SliceW>>::JoinHandle>],
) -> Result<usize, BrotliEncoderThreadError> where <Alloc as Allocator<u8>>::AllocatedMemory: Send {
  CompressMulti(params, owned_input, output, alloc_per_thread, MultiThreadedSpawner::default())
}
<|endoftext|>


<commit_before>use std::collections::{BTreeMap, HashSet};
use std::io::Read;
use std::fs::{File};
use std::os::unix::fs::MetadataExt;
use std::path::Path;
use std::str::from_utf8;

use base64;
use blake2::{Blake2b, digest::VariableOutput, digest::Input};
use failure::{Error, ResultExt};
use ssh_keys::{PrivateKey, openssh};

use lithos::nacl;
use lithos::sandbox_config::SandboxConfig;
use lithos::child_config::ChildInstance;

fn parse_private_key(filename: &Path) -> Result<Vec<PrivateKey>, Error> {
    let mut buf = String::with_capacity(1024);
    let mut f = File::open(filename)
        .context(Path::new(filename).display().to_string())?;
    let meta = f.metadata()
        .context(Path::new(filename).display().to_string())?;
    if meta.uid() != 0 {
        bail!("Key must be owned by root");
    }
    if meta.mode() & 0o777 & !0o600 != 0 {
        bail!("Key's mode must be 0600");
    }
    f.read_to_string(&mut buf)
        .context(Path::new(filename).display().to_string())?;
    Ok(openssh::parse_private_key(&buf)?)
}

fn b2_short_hash(data: &[u8]) -> String {
    let mut buf = [0u8; 6];
    let mut hash: Blake2b = VariableOutput::new(buf.len()).expect("blake2b");
    hash.process(data);
    hash.variable_result(&mut buf[..]).expect("blake2b");
    return base64::encode(&buf[..])
}

fn decrypt(key: &PrivateKey, namespaces: &HashSet<&str>, value: &str)
    -> Result<String, Error>
{
    let key_bytes = match *key {
        PrivateKey::Ed25519(key) => key,
        _ => bail!("Only ed25519 keys are supported"),
    };
    if !value.starts_with("v2:") {
        bail!("Only v2 secrets are supported");
    }
    let mut it = value.split(":");
    it.next(); // skip version
    let (key_hash, ns_hash, secr_hash, cipher) = {
        match (it.next(), it.next(), it.next(), it.next(), it.next()) {
            (Some(key), Some(ns), Some(secr), Some(cipher), None) => {
                (key, ns, secr, base64::decode(cipher)?)
            }
            _ => bail!("invalid key format"),
        }
    };

    let plain = nacl::crypto_box_edwards_seal_open(
        &cipher, public_key, private_key)?;

    let mut pair = plain.splitn(2, |&x| x == b':');
    let namespace = from_utf8(pair.next().unwrap())
        .map_err(|_| format_err!("can't decode namespace from utf-8"))?;
    let secret = pair.next().ok_or(format_err!("decrypted data is invalid"))?;

    if b2_short_hash(public_key) != key_hash {
        bail!("invalid key hash");
    }
    if b2_short_hash(namespace.as_bytes()) != ns_hash {
        bail!("invalid namespace hash");
    }
    if b2_short_hash(&secret) != secr_hash {
        bail!("invalid secret hash");
    }
    if !namespaces.contains(namespace) {
        bail!("expected namespaces {:?} got {:?}", namespaces, namespace);
    }
    if secret.contains(&0) {
        bail!("no null bytes allowed in secret");
    }

    String::from_utf8(secret.to_vec())
        .map_err(|_| format_err!("Can't decode secret as utf-8"))
}

fn decrypt_pair(keys: &[PrivateKey], namespaces: &HashSet<&str>,
    values: &[String])
    -> Result<String, Vec<Error>>
{
    let mut errs = Vec::new();
    for key in keys {
        for value in values {
            match decrypt(key, namespaces, value) {
                Ok(value) => return Ok(value),
                Err(e) => errs.push(e),
            }
        }
    }
    Err(errs)
}

pub fn decode(sandbox: &SandboxConfig, child_config: &ChildInstance,
    secrets: &BTreeMap<String, Vec<String>>)
    -> Result<BTreeMap<String, String>, Error>
{
    if secrets.len() == 0 {
        // do not read keys
        return Ok(BTreeMap::new());
    }

    let keys = if let Some(ref filename) = sandbox.secrets_private_key {
        parse_private_key(&filename)?
    } else {
        bail!("No secrets key file defined to decode secrets: {:?}",
            secrets.keys());
    };

    let mut all_namespaces = HashSet::new();
    if sandbox.secrets_namespaces.len() == 0 {
        all_namespaces.insert("");
    } else {
        all_namespaces.extend(
            sandbox.secrets_namespaces.iter().map(|x| &x[..]))
    };
    all_namespaces.extend(
        child_config.extra_secrets_namespaces.iter().map(|x| &x[..]));

    let mut res = BTreeMap::new();

    for (name, values) in secrets {
        res.insert(name.clone(), decrypt_pair(&keys, &all_namespaces, values)
            .map_err(|e| {
                format_err!("Can't decrypt secret {:?}, errors: {}", name,
                    e.iter().map(|x| x.to_string())
                    .collect::<Vec<_>>().join(", "))
            })?);
    }

    Ok(res)
}
<commit_msg>Fix typo<commit_after>use std::collections::{BTreeMap, HashSet};
use std::io::Read;
use std::fs::{File};
use std::os::unix::fs::MetadataExt;
use std::path::Path;
use std::str::from_utf8;

use base64;
use blake2::{Blake2b, digest::VariableOutput, digest::Input};
use failure::{Error, ResultExt};
use ssh_keys::{PrivateKey, openssh};

use lithos::nacl;
use lithos::sandbox_config::SandboxConfig;
use lithos::child_config::ChildInstance;

fn parse_private_key(filename: &Path) -> Result<Vec<PrivateKey>, Error> {
    let mut buf = String::with_capacity(1024);
    let mut f = File::open(filename)
        .context(Path::new(filename).display().to_string())?;
    let meta = f.metadata()
        .context(Path::new(filename).display().to_string())?;
    if meta.uid() != 0 {
        bail!("Key must be owned by root");
    }
    if meta.mode() & 0o777 & !0o600 != 0 {
        bail!("Key's mode must be 0600");
    }
    f.read_to_string(&mut buf)
        .context(Path::new(filename).display().to_string())?;
    Ok(openssh::parse_private_key(&buf)?)
}

fn b2_short_hash(data: &[u8]) -> String {
    let mut buf = [0u8; 6];
    let mut hash: Blake2b = VariableOutput::new(buf.len()).expect("blake2b");
    hash.process(data);
    hash.variable_result(&mut buf[..]).expect("blake2b");
    return base64::encode(&buf[..])
}

fn decrypt(key: &PrivateKey, namespaces: &HashSet<&str>, value: &str)
    -> Result<String, Error>
{
    let key_bytes = match *key {
        PrivateKey::Ed25519(key) => key,
        _ => bail!("Only ed25519 keys are supported"),
    };
    let (private_key, public_key) = key_bytes.split_at(32);
    if !value.starts_with("v2:") {
        bail!("Only v2 secrets are supported");
    }
    let mut it = value.split(":");
    it.next(); // skip version
    let (key_hash, ns_hash, secr_hash, cipher) = {
        match (it.next(), it.next(), it.next(), it.next(), it.next()) {
            (Some(key), Some(ns), Some(secr), Some(cipher), None) => {
                (key, ns, secr, base64::decode(cipher)?)
            }
            _ => bail!("invalid key format"),
        }
    };

    let plain = nacl::crypto_box_edwards_seal_open(
        &cipher, public_key, private_key)?;

    let mut pair = plain.splitn(2, |&x| x == b':');
    let namespace = from_utf8(pair.next().unwrap())
        .map_err(|_| format_err!("can't decode namespace from utf-8"))?;
    let secret = pair.next().ok_or(format_err!("decrypted data is invalid"))?;

    if b2_short_hash(public_key) != key_hash {
        bail!("invalid key hash");
    }
    if b2_short_hash(namespace.as_bytes()) != ns_hash {
        bail!("invalid namespace hash");
    }
    if b2_short_hash(&secret) != secr_hash {
        bail!("invalid secret hash");
    }
    if !namespaces.contains(namespace) {
        bail!("expected namespaces {:?} got {:?}", namespaces, namespace);
    }
    if secret.contains(&0) {
        bail!("no null bytes allowed in secret");
    }

    String::from_utf8(secret.to_vec())
        .map_err(|_| format_err!("Can't decode secret as utf-8"))
}

fn decrypt_pair(keys: &[PrivateKey], namespaces: &HashSet<&str>,
    values: &[String])
    -> Result<String, Vec<Error>>
{
    let mut errs = Vec::new();
    for key in keys {
        for value in values {
            match decrypt(key, namespaces, value) {
                Ok(value) => return Ok(value),
                Err(e) => errs.push(e),
            }
        }
    }
    Err(errs)
}

pub fn decode(sandbox: &SandboxConfig, child_config: &ChildInstance,
    secrets: &BTreeMap<String, Vec<String>>)
    -> Result<BTreeMap<String, String>, Error>
{
    if secrets.len() == 0 {
        // do not read keys
        return Ok(BTreeMap::new());
    }

    let keys = if let Some(ref filename) = sandbox.secrets_private_key {
        parse_private_key(&filename)?
    } else {
        bail!("No secrets key file defined to decode secrets: {:?}",
            secrets.keys());
    };

    let mut all_namespaces = HashSet::new();
    if sandbox.secrets_namespaces.len() == 0 {
        all_namespaces.insert("");
    } else {
        all_namespaces.extend(
            sandbox.secrets_namespaces.iter().map(|x| &x[..]))
    };
    all_namespaces.extend(
        child_config.extra_secrets_namespaces.iter().map(|x| &x[..]));

    let mut res = BTreeMap::new();

    for (name, values) in secrets {
        res.insert(name.clone(), decrypt_pair(&keys, &all_namespaces, values)
            .map_err(|e| {
                format_err!("Can't decrypt secret {:?}, errors: {}", name,
                    e.iter().map(|x| x.to_string())
                    .collect::<Vec<_>>().join(", "))
            })?);
    }

    Ok(res)
}
<|endoftext|>


<commit_before>// Code based on [https://github.com/defuz/sublimate/blob/master/src/core/syntax/scope.rs](https://github.com/defuz/sublimate/blob/master/src/core/syntax/scope.rs)
// released under the MIT license by @defuz
use bitflags::bitflags;

/// Foreground and background colors, with font style
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct Style {
    /// Foreground color
    pub foreground: Color,
    /// Background color
    pub background: Color,
    /// Style of the font
    pub font_style: FontStyle,
}

/// A change to a [`Style`] applied incrementally by a theme rule
///
/// Fields left empty (as `None`) will not modify the corresponding field on a `Style`
///
/// [`Style`]: struct.Style.html
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct StyleModifier {
    /// Foreground color
    pub foreground: Option<Color>,
    /// Background color
    pub background: Option<Color>,
    /// Style of the font
    pub font_style: Option<FontStyle>,
}

/// RGBA color, directly from the theme
///
/// Because these numbers come directly from the theme, you might have to do your own color space
/// conversion if you're outputting a different color space from the theme. This can be a problem
/// because some Sublime themes use sRGB and some don't. This is specified in an attribute syntect
/// doesn't parse yet.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct Color {
    /// Red component
    pub r: u8,
    /// Green component
    pub g: u8,
    /// Blue component
    pub b: u8,
    /// Alpha (transparency) component
    pub a: u8,
}

bitflags! {
    /// The color-independent styling of a font - i.e. bold, italicized, and/or underlined
    #[derive(Serialize, Deserialize)]
    pub struct FontStyle: u8 {
        /// Bold font style
        const BOLD = 1;
        /// Underline font style
        const UNDERLINE = 2;
        /// Italic font style
        const ITALIC = 4;
    }
}

impl Color {
    /// The color black (`#000000`)
    pub const BLACK: Color = Color {
        r: 0x00,
        g: 0x00,
        b: 0x00,
        a: 0xFF,
    };

    /// The color white (`#FFFFFF`)
    pub const WHITE: Color = Color {
        r: 0xFF,
        g: 0xFF,
        b: 0xFF,
        a: 0xFF,
    };
}

impl Style {
    /// Applies a change to this style, yielding a new changed style
    pub fn apply(&self, modifier: StyleModifier) -> Style {
        Style {
            foreground: modifier.foreground.unwrap_or(self.foreground),
            background: modifier.background.unwrap_or(self.background),
            font_style: modifier.font_style.unwrap_or(self.font_style),
        }
    }
}

impl Default for Style {
    fn default() -> Style {
        Style {
            foreground: Color::BLACK,
            background: Color::WHITE,
            font_style: FontStyle::empty(),
        }
    }
}

impl StyleModifier {
    /// Applies the other modifier to this one, creating a new modifier.
    ///
    /// Values in `other` are preferred.
    pub fn apply(&self, other: StyleModifier) -> StyleModifier {
        StyleModifier {
            foreground: other.foreground.or(self.foreground),
            background: other.background.or(self.background),
            font_style: other.font_style.or(self.font_style),
        }
    }
}
<commit_msg>Derive Hash for Color and Style<commit_after>// Code based on [https://github.com/defuz/sublimate/blob/master/src/core/syntax/scope.rs](https://github.com/defuz/sublimate/blob/master/src/core/syntax/scope.rs)
// released under the MIT license by @defuz
use bitflags::bitflags;

/// Foreground and background colors, with font style
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Style {
    /// Foreground color
    pub foreground: Color,
    /// Background color
    pub background: Color,
    /// Style of the font
    pub font_style: FontStyle,
}

/// A change to a [`Style`] applied incrementally by a theme rule
///
/// Fields left empty (as `None`) will not modify the corresponding field on a `Style`
///
/// [`Style`]: struct.Style.html
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct StyleModifier {
    /// Foreground color
    pub foreground: Option<Color>,
    /// Background color
    pub background: Option<Color>,
    /// Style of the font
    pub font_style: Option<FontStyle>,
}

/// RGBA color, directly from the theme
///
/// Because these numbers come directly from the theme, you might have to do your own color space
/// conversion if you're outputting a different color space from the theme. This can be a problem
/// because some Sublime themes use sRGB and some don't. This is specified in an attribute syntect
/// doesn't parse yet.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Color {
    /// Red component
    pub r: u8,
    /// Green component
    pub g: u8,
    /// Blue component
    pub b: u8,
    /// Alpha (transparency) component
    pub a: u8,
}

bitflags! {
    /// The color-independent styling of a font - i.e. bold, italicized, and/or underlined
    #[derive(Serialize, Deserialize)]
    pub struct FontStyle: u8 {
        /// Bold font style
        const BOLD = 1;
        /// Underline font style
        const UNDERLINE = 2;
        /// Italic font style
        const ITALIC = 4;
    }
}

impl Color {
    /// The color black (`#000000`)
    pub const BLACK: Color = Color {
        r: 0x00,
        g: 0x00,
        b: 0x00,
        a: 0xFF,
    };

    /// The color white (`#FFFFFF`)
    pub const WHITE: Color = Color {
        r: 0xFF,
        g: 0xFF,
        b: 0xFF,
        a: 0xFF,
    };
}

impl Style {
    /// Applies a change to this style, yielding a new changed style
    pub fn apply(&self, modifier: StyleModifier) -> Style {
        Style {
            foreground: modifier.foreground.unwrap_or(self.foreground),
            background: modifier.background.unwrap_or(self.background),
            font_style: modifier.font_style.unwrap_or(self.font_style),
        }
    }
}

impl Default for Style {
    fn default() -> Style {
        Style {
            foreground: Color::BLACK,
            background: Color::WHITE,
            font_style: FontStyle::empty(),
        }
    }
}

impl StyleModifier {
    /// Applies the other modifier to this one, creating a new modifier.
    ///
    /// Values in `other` are preferred.
    pub fn apply(&self, other: StyleModifier) -> StyleModifier {
        StyleModifier {
            foreground: other.foreground.or(self.foreground),
            background: other.background.or(self.background),
            font_style: other.font_style.or(self.font_style),
        }
    }
}
<|endoftext|>


<commit_before>//! Event identifiers.

/// Event id for after render event.
pub const AFTER_RENDER: EventId = EventId("piston/after_render");
/// Event id for controller axis event.
pub const CONTROLLER_AXIS: EventId = EventId("piston/controller_axis");
/// Event id for cursor event.
pub const CURSOR: EventId = EventId("piston/cursor");
/// Event id for focus event.
pub const FOCUS: EventId = EventId("piston/focus");
/// Event id for close event.
pub const CLOSE: EventId = EventId("piston/close");
/// Event id for idle event.
pub const IDLE: EventId = EventId("piston/idle");
/// Event id for mouse scroll event.
pub const MOUSE_SCROLL: EventId = EventId("piston/mouse_scroll");
/// Event id for mouse relative event.
pub const MOUSE_RELATIVE: EventId = EventId("piston/mouse_relative");
/// Event id for mouse cursor event.
pub const MOUSE_CURSOR: EventId = EventId("piston/mouse_cursor");
/// Event id for button event.
pub const BUTTON: EventId = EventId("piston/button");
/// Event id for render event.
pub const RENDER: EventId = EventId("piston/render");
/// Event id for resize event.
pub const RESIZE: EventId = EventId("piston/resize");
/// Event id for text event.
pub const TEXT: EventId = EventId("piston/text");
/// Event id for touch event.
pub const TOUCH: EventId = EventId("piston/touch");
/// Event id for update event.
pub const UPDATE: EventId = EventId("piston/update");
/// Event id for file drag event.
pub const FILE_DRAG: EventId = EventId("piston/file_drag");

/// Used to identify events arguments provided by traits.
///
/// Use format `<api>/<event>` to avoid naming collision.
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub struct EventId(pub &'static str);
<commit_msg>Derive `PartialOrd`, `Ord`, `Hash` for `EventId`<commit_after>//! Event identifiers.

/// Event id for after render event.
pub const AFTER_RENDER: EventId = EventId("piston/after_render");
/// Event id for controller axis event.
pub const CONTROLLER_AXIS: EventId = EventId("piston/controller_axis");
/// Event id for cursor event.
pub const CURSOR: EventId = EventId("piston/cursor");
/// Event id for focus event.
pub const FOCUS: EventId = EventId("piston/focus");
/// Event id for close event.
pub const CLOSE: EventId = EventId("piston/close");
/// Event id for idle event.
pub const IDLE: EventId = EventId("piston/idle");
/// Event id for mouse scroll event.
pub const MOUSE_SCROLL: EventId = EventId("piston/mouse_scroll");
/// Event id for mouse relative event.
pub const MOUSE_RELATIVE: EventId = EventId("piston/mouse_relative");
/// Event id for mouse cursor event.
pub const MOUSE_CURSOR: EventId = EventId("piston/mouse_cursor");
/// Event id for button event.
pub const BUTTON: EventId = EventId("piston/button");
/// Event id for render event.
pub const RENDER: EventId = EventId("piston/render");
/// Event id for resize event.
pub const RESIZE: EventId = EventId("piston/resize");
/// Event id for text event.
pub const TEXT: EventId = EventId("piston/text");
/// Event id for touch event.
pub const TOUCH: EventId = EventId("piston/touch");
/// Event id for update event.
pub const UPDATE: EventId = EventId("piston/update");
/// Event id for file drag event.
pub const FILE_DRAG: EventId = EventId("piston/file_drag");

/// Used to identify events arguments provided by traits.
///
/// Use format `<api>/<event>` to avoid naming collision.
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
pub struct EventId(pub &'static str);
<|endoftext|>


<commit_before>use std::fs::File;
use std::io::{ BufReader, BufWriter, Read, Write };
use std::path::Path;
use toml::{ to_string as toml_to_string, Value };

use error::PackageError;
use installation::PackageState;
use utils::Hash;

#[derive(Serialize, Deserialize)]
pub struct Cache(Vec<(Hash, PackageState)>);

impl Cache {
    pub fn read_from<P: AsRef<Path>>(path: P) -> Result<Cache, PackageError> {
        let file = File::open(path)?;
        let mut file = BufReader::new(file);
        let mut content = String::new();
        file.read_to_string(&mut content);
        let content = content.trim().parse::<Value>()?;
        Ok(content.try_into::<Self>()?)
    }

    pub fn write_to<P: AsRef<Path>>(&self, path: P) -> Result<(), PackageError> {
        let file = File::open(path)?;
        let mut file = BufWriter::new(file);
        let content = toml_to_string(self)?;
        Ok(file.write_all(content.as_bytes())?)
    }
}<commit_msg>Fixing a typo<commit_after>use std::fs::File;
use std::io::{ BufReader, BufWriter, Read, Write };
use std::path::Path;
use toml::{ to_string as toml_to_string, Value };

use error::PackageError;
use installation::PackageState;
use utils::Hash;

#[derive(Serialize, Deserialize)]
pub struct Cache(Vec<(Hash, PackageState)>);

impl Cache {
    pub fn read_from<P: AsRef<Path>>(path: P) -> Result<Cache, PackageError> {
        let file = File::open(path)?;
        let mut file = BufReader::new(file);
        let mut content = String::new();
        file.read_to_string(&mut content);
        let content = content.trim().parse::<Value>()?;
        Ok(content.try_into::<Self>()?)
    }

    pub fn write_to<P: AsRef<Path>>(&self, path: P) -> Result<(), PackageError> {
        let file = File::create(path)?;
        let mut file = BufWriter::new(file);
        let content = toml_to_string(self)?;
        Ok(file.write_all(content.as_bytes())?)
    }
}<|endoftext|>


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

//! Implementation of ElasticSearch Mapping operation

use std::collections::HashMap;

use ::Client;
use ::error::EsError;

pub type Properties = HashMap<String, HashMap<String, String>>;

/// An indexing operation
pub struct MappingOperation<'a, 'b> {
    /// The HTTP client that this operation will use
    client:     &'a mut Client,

    /// The index that will be created and eventually mapped
    index:      &'b str,

    /// The actual mapping
    properties: &'b Properties
}

impl<'a, 'b> MappingOperation<'a, 'b> {
    pub fn new(client: &'a mut Client, index: &'b str, properties: &'b Properties) -> MappingOperation<'a, 'b> {
        MappingOperation {
            client:     client,
            index:      index,
            properties: properties
        }
    }

    pub fn send(&'b mut self) -> Result<MappingResult, EsError> {
        let body = hashmap! {
            "mappings" => hashmap! {
                "sample" => hashmap! {
                    "properties" => self.properties
                }
            }
        };

        let url = format!("{}", self.index);
        let (_, _) = try!(self.client.put_body_op(&url, &body));
        Ok(MappingResult)
    }
}

/// The result of a mapping operation
#[derive(Debug)]
pub struct MappingResult;

#[cfg(test)]
pub mod tests {
    extern crate env_logger;

    use std::collections::HashMap;

    use super::MappingOperation;

    #[test]
    fn test_mapping() {
        let index_name = "tests_test_mapping";
        let mut client = ::tests::make_client();

        client.delete_op(&format!("/{}", index_name)).unwrap();

        let mapping = hashmap! {
            "created_at".to_owned() => hashmap! {
                "type".to_owned() => "date".to_owned(),
                "format".to_owned() => "epoch_second".to_owned()
            },

            "title".to_owned() => hashmap! {
                "type".to_owned() => "string".to_owned(),
                "index".to_owned() => "not_analyzed".to_owned()
            }
        };

        let result = MappingOperation::new(&mut client, index_name, &mapping).send();
        assert!(result.is_ok());
    }
}
<commit_msg>Let Properties accept string slices and avoid using epoch in tests<commit_after>/*
 * Copyright 2016 Ben Ashford
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

//! Implementation of ElasticSearch Mapping operation

use std::collections::HashMap;

use ::Client;
use ::error::EsError;

pub type Properties<'a> = HashMap<&'a str, HashMap<&'a str, &'a str>>;

/// An indexing operation
pub struct MappingOperation<'a, 'b> {
    /// The HTTP client that this operation will use
    client:     &'a mut Client,

    /// The index that will be created and eventually mapped
    index:      &'b str,

    /// The actual mapping
    properties: &'b Properties<'b>
}

impl<'a, 'b> MappingOperation<'a, 'b> {
    pub fn new(client: &'a mut Client, index: &'b str, properties: &'b Properties) -> MappingOperation<'a, 'b> {
        MappingOperation {
            client:     client,
            index:      index,
            properties: properties
        }
    }

    pub fn send(&'b mut self) -> Result<MappingResult, EsError> {
        let body = hashmap! {
            "mappings" => hashmap! {
                "sample" => hashmap! {
                    "properties" => self.properties
                }
            }
        };

        let url = format!("{}", self.index);
        let (_, _) = try!(self.client.put_body_op(&url, &body));
        Ok(MappingResult)
    }
}

/// The result of a mapping operation
#[derive(Debug)]
pub struct MappingResult;

#[cfg(test)]
pub mod tests {
    extern crate env_logger;

    use std::collections::HashMap;

    use super::MappingOperation;

    #[test]
    fn test_mapping() {
        let index_name = "tests_test_mapping";
        let mut client = ::tests::make_client();

        client.delete_op(&format!("/{}", index_name)).unwrap();

        let mapping = hashmap! {
            "created_at" => hashmap! {
                "type" => "date",
                "format" => "strict_date_optional_time"
            },

            "title" => hashmap! {
                "type" => "string",
                "index" => "not_analyzed"
            }
        };

        let result = MappingOperation::new(&mut client, index_name, &mapping).send();
        assert!(result.is_ok());
    }
}
<|endoftext|>


<commit_before>use parser::Inline;
use parser::attributes::parse_inline_attributes;
use parser::inline::parse_inline_elements;
use parser::patterns::LINK_PATTERN;

pub fn parse_link(text: &str) -> Option<(Inline, usize)> {
    if LINK_PATTERN.is_match(text) {
        let caps = LINK_PATTERN.captures(text).unwrap();
        let (attrs, text) = parse_inline_attributes(caps.name("string").unwrap());
        let url = caps.name("url").unwrap().to_string();

        Some((
            Inline::Link {
                attributes: attrs,
                description: parse_inline_elements(&[&*text]),
                url: url,
            },
            caps.at(0).unwrap().len()
        ))
    } else {
        None
    }
}

#[cfg(test)]
mod tests {
    use parser::{Attributes, Inline, ItalicTagType};
    use super::*;

    #[test]
    fn parses_italic_text_correctly() {
        assert_eq!(
            parse_link("\"_Text_\":http://example.com"),
            Some((
                Inline::Link {
                    attributes: Attributes::new(),
                    description: vec![
                        Inline::Italic {
                            attributes: Attributes::new(),
                            elements: vec![
                                Inline::Text("Text".to_string())
                            ],
                            tag_type: ItalicTagType::Emphasis,
                        }
                    ],
                    url: "http://example.com".to_string(),
                },
                27
            ))
        );
    }
}
<commit_msg>Fix test case name in link parser<commit_after>use parser::Inline;
use parser::attributes::parse_inline_attributes;
use parser::inline::parse_inline_elements;
use parser::patterns::LINK_PATTERN;

pub fn parse_link(text: &str) -> Option<(Inline, usize)> {
    if LINK_PATTERN.is_match(text) {
        let caps = LINK_PATTERN.captures(text).unwrap();
        let (attrs, text) = parse_inline_attributes(caps.name("string").unwrap());
        let url = caps.name("url").unwrap().to_string();

        Some((
            Inline::Link {
                attributes: attrs,
                description: parse_inline_elements(&[&*text]),
                url: url,
            },
            caps.at(0).unwrap().len()
        ))
    } else {
        None
    }
}

#[cfg(test)]
mod tests {
    use parser::{Attributes, Inline, ItalicTagType};
    use super::*;

    #[test]
    fn parses_link_correctly() {
        assert_eq!(
            parse_link("\"_Text_\":http://example.com"),
            Some((
                Inline::Link {
                    attributes: Attributes::new(),
                    description: vec![
                        Inline::Italic {
                            attributes: Attributes::new(),
                            elements: vec![
                                Inline::Text("Text".to_string())
                            ],
                            tag_type: ItalicTagType::Emphasis,
                        }
                    ],
                    url: "http://example.com".to_string(),
                },
                27
            ))
        );
    }
}
<|endoftext|>


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

extern crate irc as irclib;
extern crate evalbotlib as backend;
extern crate rustc_serialize;

use std::thread;
use std::time::Duration;
use std::sync::mpsc;

use backend::{EvalSvc, EvalSvcCfg, Response, util};

use self::irclib::client::server::NetIrcServer;
use self::irclib::client::prelude as ircp;
use self::irclib::client::prelude::{Server, ServerExt};

macro_rules! send_msg {
    ($conn:expr, $dest:expr, $line:expr) => {
        if let Err(x) = $conn.send_privmsg($dest, $line) {
            println!("failed to send message: {}", x)
        }
    };
}

macro_rules! send_notice {
    ($conn:expr, $dest:expr, $line:expr) => {
        if let Err(x) = $conn.send_notice($dest, $line) {
            println!("failed to send notice: {}", x)
        }
    };
}

type EvalWorkerSender = mpsc::Sender<(IrcMessage, SendMsgFnBox)>;
type EvalWorkerReceiver = mpsc::Receiver<(IrcMessage, SendMsgFnBox)>;
type SendMsgFnBox = Box<Fn(&str, &str) + Send>;

#[derive(Clone, RustcDecodable, Default, PartialEq, Debug)]
struct IrcCfg {
    output: OutputCfg,
    command_prefix: String,
    owners: Vec<String>,
    irc_networks: Vec<ircp::Config>
}

#[derive(Clone, RustcDecodable, Default, PartialEq, Debug)]
struct OutputCfg {
    chan_output_prefix: String,
    max_channel_lines: usize,
    max_channel_line_len: usize,
    max_priv_lines: usize
}

fn main() {
    let config = match util::decode::<IrcCfg>("evalbot.irc.toml") {
        Ok(x) => x,
        Err(x) => {
            println!("failed to read evalbot.irc.toml: {:?}", x);
            return;
        }
    };
    let (tx, rx) = mpsc::channel();

    {
        let svc = match start_evalsvc() {
            Ok(x) => x,
            Err(()) => return,
        };
        let ocfg = config.output.clone();
        thread::spawn(move || eval_worker(svc, rx, ocfg));
    }

    let (qtx, qrx) = mpsc::channel();

    for (k, nwk) in config.irc_networks
                          .iter()
                          .filter_map(|cfg| {
                              match ircp::IrcServer::from_config(cfg.clone()) {
                                  Ok(conn) => Some(conn),
                                  Err(ref err) => {
                                      println!("failed to create IRC connection from Config {:#?}: {:?}", cfg, err);
                                      None
                                  }
                              }
                          })
                          .enumerate() {
        start_worker(nwk,
                     tx.clone(),
                     config.owners.clone(),
                     config.command_prefix.clone(),
                     qtx.clone(),
                     format!("{}", k));
    }
    util::ignore(qrx.recv());
}

fn start_evalsvc() -> Result<EvalSvc, ()> {
    let evalcfg = match util::decode::<EvalSvcCfg>("evalbot.toml") {
        Ok(x) => x,
        Err(x) => {
            println!("failed to read evalbot.toml: {:?}", x);
            return Err(());
        }
    };
    Ok(EvalSvc::new(evalcfg))
}

fn start_worker(conn: NetIrcServer,
    svc: EvalWorkerSender,
    owners: Vec<String>,
    cmd_prefix: String,
    quit_handle: mpsc::Sender<()>,
    conn_hash: String)
                -> thread::JoinHandle<()> {
    thread::spawn(move || {
        worker(conn, svc, owners, cmd_prefix, quit_handle, conn_hash);
    })
}

#[derive(Clone, Debug)]
struct MessageSource {
    chan: Option<String>,
    sender: String,
    conn_hash: String
}

impl MessageSource {
    fn reply_to(&self) -> &str {
        if let Some(ref chan) = self.chan { chan } else { &self.sender }
    }

    fn is_chan(&self) -> bool {
        self.chan.is_some()
    }
}

#[derive(Debug)]
struct IrcMessage {
    sender: MessageSource,
    data: MessageData
}

#[derive(Debug)]
enum MessageData {
    EvalReq {
        lang: String,
        code: String,
        timeout: bool
    },
    Quit,
    Rehash,
    Restart {
        lang: String
    },
    Raw {
        msg: String
    }
}

unsafe impl Sync for IrcMessage {}

fn parse_msg(conn_hash: &str, message: &ircp::Message, owners: &[String], cmd_prefix: &str) -> Option<IrcMessage> {
    let sender = message.source_nickname().unwrap_or("").to_owned();
    if let ircp::Command::PRIVMSG(ref target, ref message) = message.command {
        let target = target.to_owned();
        let is_owner = owners.iter().any(|x| *x == sender);
        let sender = {
            let in_channel = target.starts_with('#');
            if !in_channel && message.contains('\x01') {
                // ignore CTCP
                return None;
            }

            MessageSource {
                chan: if in_channel { Some(target) } else { None },
                sender: sender,
                conn_hash: conn_hash.to_owned()
            }
        };

        if is_owner {
            if let Some(x) = try_cmd(&message, cmd_prefix) {
                return Some(IrcMessage { sender: sender, data: x });
            }
        }

        let tok: Vec<&str> = message.trim().splitn(2, '>').collect();
        if tok.len() == 2 {
            return Some(IrcMessage {
                sender: sender,
                data: MessageData::EvalReq { timeout: true, lang: tok[0].to_owned(), code: tok[1].to_owned() }
            });
        } else if !is_owner {
            return None;
        }

        let tok: Vec<&str> = message.trim().splitn(2, '#').collect();
        if tok.len() == 2 {
            Some(IrcMessage {
                sender: sender,
                data: MessageData::EvalReq { timeout: false, lang: tok[0].to_owned(), code: tok[1].to_owned() }
            })
        } else {
            None
        }
    } else {
        None
    }
}

fn try_cmd(msg: &str, cmd_prefix: &str) -> Option<MessageData> {
    if let Some((cmd, args)) = parse_cmd(msg, cmd_prefix) {
        match &cmd as &str {
            "quit" => Some(MessageData::Quit),
            "rehash" => Some(MessageData::Rehash),
            "restart" if !args.is_empty() => Some(MessageData::Restart { lang: args[0].to_owned() }),
            "raw" if !args.is_empty() => {
                Some(MessageData::Raw {
                    msg: {
                        let mut m = args.join(" ");
                        m.push_str("\r\n");
                        m
                    }
                })
            }
            _ => None,
        }
    } else {
        None
    }
}

fn parse_cmd(msg: &str, cmd_prefix: &str) -> Option<(String, Vec<String>)> {
    let tok: Vec<&str> = match msg.splitn(2, cmd_prefix).nth(1).map(|x| x.split(' ').collect()) {
        Some(x) => x,
        None => return None,
    };

    if tok.is_empty() {
        return None;
    }

    Some((tok[0].to_owned(),
          tok.into_iter().skip(1).map(|x| x.to_owned()).collect::<Vec<String>>()))
}

fn worker(conn: NetIrcServer,
    tx: EvalWorkerSender,
    owners: Vec<String>,
    cmd_prefix: String,
    quit_handle: mpsc::Sender<()>,
    conn_hash: String) {
    'connection: loop {
        println!("connecting to to {:?} as {:?}", conn.config().server, conn.config().nickname);
        while let Err(x) = conn.identify() {
            println!("error while identify()ing; retrying: {:?}", x);
            thread::sleep(Duration::new(1, 0));
        }
        println!("connected to {:?} as {:?}", conn.config().server, conn.config().nickname);
        for msg in conn.iter() {
            let msg = match msg {
                Ok(x) => x,
                Err(_) => continue,
            };

            if let Some(msg) = parse_msg(&conn_hash, &msg, &owners, &cmd_prefix) {
                println!("M: {:?}", msg);
                match msg.data {
                    MessageData::EvalReq { .. } | MessageData::Rehash | MessageData::Restart { .. } => {
                        let conn = conn.clone();
                        util::ignore(tx.send((msg, Box::new(move |to, r| send_msg!(conn, to, r)))))
                    }
                    MessageData::Raw { msg: raw_msg } => {
                        match raw_msg.parse::<ircp::Message>() {
                            Ok(raw_msg) => {
                                println!("{} sent raw: {:?}", msg.sender.sender, raw_msg);
                                if let Err(err) = conn.send(raw_msg) {
                                    send_notice!(conn, &msg.sender.sender, &format!("error: {:?}", err))
                                }
                            }
                            Err(err) => send_notice!(conn, &msg.sender.sender, &format!("error: {:?}", err)),
                        };
                    }
                    MessageData::Quit => {
                        util::ignore(quit_handle.send(()));
                        break 'connection;
                    }
                }
            }
        }
    }
}

fn eval_worker(mut svc: EvalSvc, rx: EvalWorkerReceiver, ocfg: OutputCfg) {
    while let Ok((msg, callback)) = rx.recv() {
        macro_rules! reply {
            ($x:expr) => { callback(msg.sender.reply_to(), $x); }
        }
        match msg.data {
            MessageData::EvalReq { lang, code, timeout } => {
                let ocfg = ocfg.clone();
                let sender = msg.sender.clone();
                svc.eval(&lang,
                         code,
                         timeout,
                         Some(format!("{}{}", msg.sender.conn_hash, msg.sender.reply_to())),
                         Box::new(move |resp| respond(resp, ocfg, sender, callback)))
            }
            MessageData::Rehash => {
                if let Ok(nsvc) = start_evalsvc() {
                    reply!("rehashed");
                    svc = nsvc;
                } else {
                    reply!("read config failed; check logs");
                }
            }
            MessageData::Restart { lang } => {
                match svc.restart(&lang) {
                    Ok(_) => reply!(&format!("restarted {}", lang)),
                    Err(_) => reply!(&format!("no such language {}", lang)),
                }
            }
            _ => println!("invalid thing sent to eval_worker?"),
        };
    }
}

fn sanitise_output(input: &str, prefix: Option<&str>, max_len: usize, max_lines: usize) -> Vec<String> {
    let (mut ret, initial_lines): (Vec<String>, usize) = {
        let med = input.lines()
                       .map(|l| l.trim())
                       .filter(|l| !l.is_empty())
                       .flat_map(|l| l.split(util::LengthPattern(max_len)))
                       .collect::<Vec<_>>();

        (if let Some(prefix) = prefix {
            med.iter().take(max_lines).map(|l| prefix.to_owned() + l).collect()
        } else {
            med.iter().take(max_lines).map(|l| (*l).to_owned()).collect()
        },
         med.len())
    };
    if ret.is_empty() {
        ret.push("(ok, no output)".to_owned());
    } else if ret.len() < initial_lines {
        if let Some(ref mut l) = ret.last_mut() {
            l.push_str("... (truncated)");
        }
    }
    ret
}

#[allow(boxed_local)] // send_msg is already boxed anyway
fn respond(resp: Response, cfg: OutputCfg, to: MessageSource, send_msg: SendMsgFnBox) {
    let (response, error) = match resp {
        Response::Success(x) => (x, false),
        Response::Error(x) => (x, true),
        _ => return,
    };
    let output = sanitise_output(&response,
                                 if to.is_chan() && !error { Some(&cfg.chan_output_prefix) } else { None },
                                 if to.is_chan() { cfg.max_channel_line_len } else { 425 },
                                 if to.is_chan() { cfg.max_channel_lines } else { cfg.max_priv_lines });
    for line in &output {
        send_msg(to.reply_to(), line);
    }
}
<commit_msg>Add multiline buffering<commit_after>#![feature(plugin)]
#![plugin(clippy)]

extern crate irc as irclib;
extern crate evalbotlib as backend;
extern crate rustc_serialize;

use std::thread;
use std::time::Duration;
use std::sync::mpsc;
use std::collections::HashMap;

use backend::{EvalSvc, EvalSvcCfg, Response, util};

use self::irclib::client::server::NetIrcServer;
use self::irclib::client::prelude as ircp;
use self::irclib::client::prelude::{Server, ServerExt};

macro_rules! send_msg {
    ($conn:expr, $dest:expr, $line:expr) => {
        if let Err(x) = $conn.send_privmsg($dest, $line) {
            println!("failed to send message: {}", x)
        }
    };
}

macro_rules! send_notice {
    ($conn:expr, $dest:expr, $line:expr) => {
        if let Err(x) = $conn.send_notice($dest, $line) {
            println!("failed to send notice: {}", x)
        }
    };
}

type EvalWorkerSender = mpsc::Sender<(IrcMessage, SendMsgFnBox)>;
type EvalWorkerReceiver = mpsc::Receiver<(IrcMessage, SendMsgFnBox)>;
type SendMsgFnBox = Box<Fn(&str, &str) + Send>;

#[derive(Clone, RustcDecodable, Default, PartialEq, Debug)]
struct IrcCfg {
    output: OutputCfg,
    command_prefix: String,
    owners: Vec<String>,
    irc_networks: Vec<ircp::Config>
}

#[derive(Clone, RustcDecodable, Default, PartialEq, Debug)]
struct OutputCfg {
    chan_output_prefix: String,
    max_channel_lines: usize,
    max_channel_line_len: usize,
    max_priv_lines: usize
}

fn main() {
    let config = match util::decode::<IrcCfg>("evalbot.irc.toml") {
        Ok(x) => x,
        Err(x) => {
            println!("failed to read evalbot.irc.toml: {:?}", x);
            return;
        }
    };
    let (tx, rx) = mpsc::channel();

    {
        let svc = match start_evalsvc() {
            Ok(x) => x,
            Err(()) => return,
        };
        let ocfg = config.output.clone();
        thread::spawn(move || eval_worker(svc, rx, ocfg));
    }

    let (qtx, qrx) = mpsc::channel();

    for (k, nwk) in config.irc_networks
                          .iter()
                          .filter_map(|cfg| {
                              match ircp::IrcServer::from_config(cfg.clone()) {
                                  Ok(conn) => Some(conn),
                                  Err(ref err) => {
                                      println!("failed to create IRC connection from Config {:#?}: {:?}", cfg, err);
                                      None
                                  }
                              }
                          })
                          .enumerate() {
        start_worker(nwk,
                     tx.clone(),
                     config.owners.clone(),
                     config.command_prefix.clone(),
                     qtx.clone(),
                     format!("{}", k));
    }
    util::ignore(qrx.recv());
}

fn start_evalsvc() -> Result<EvalSvc, ()> {
    let evalcfg = match util::decode::<EvalSvcCfg>("evalbot.toml") {
        Ok(x) => x,
        Err(x) => {
            println!("failed to read evalbot.toml: {:?}", x);
            return Err(());
        }
    };
    Ok(EvalSvc::new(evalcfg))
}

fn start_worker(conn: NetIrcServer,
    svc: EvalWorkerSender,
    owners: Vec<String>,
    cmd_prefix: String,
    quit_handle: mpsc::Sender<()>,
    conn_hash: String)
                -> thread::JoinHandle<()> {
    thread::spawn(move || {
        worker(conn, svc, owners, cmd_prefix, quit_handle, conn_hash);
    })
}

#[derive(Clone, Debug, Hash, PartialEq, Eq)]
struct MessageSource {
    chan: Option<String>,
    sender: String,
    conn_hash: String
}

impl MessageSource {
    fn reply_to(&self) -> &str {
        if let Some(ref chan) = self.chan { chan } else { &self.sender }
    }

    fn is_chan(&self) -> bool {
        self.chan.is_some()
    }
}

#[derive(Debug)]
struct IrcMessage {
    sender: MessageSource,
    data: MessageData
}

#[derive(Debug)]
enum MessageData {
    EvalReq {
        lang: String,
        code: String,
        timeout: bool
    },
    Quit,
    Rehash,
    Restart {
        lang: String
    },
    Raw {
        msg: String
    },
    Multiline {
        lang: String,
        code: String
    },
    CancelMultiline { lang: String }
}

unsafe impl Sync for IrcMessage {}

fn parse_msg(conn_hash: &str, message: &ircp::Message, owners: &[String], cmd_prefix: &str) -> Option<IrcMessage> {
    let sender = message.source_nickname().unwrap_or("").to_owned();
    if let ircp::Command::PRIVMSG(ref target, ref message) = message.command {
        let target = target.to_owned();
        let is_owner = owners.iter().any(|x| *x == sender);
        let sender = {
            let in_channel = target.starts_with('#');
            if !in_channel && message.contains('\x01') {
                // ignore CTCP
                return None;
            }

            MessageSource {
                chan: if in_channel { Some(target) } else { None },
                sender: sender,
                conn_hash: conn_hash.to_owned()
            }
        };

        if let Some(x) = try_cmd(&message, cmd_prefix, is_owner) {
            return Some(IrcMessage { sender: sender, data: x });
        }

        let message = message.trim();
        let tok: Vec<&str> = message.splitn(2, |c| c == '>' || c == '#' || c == '$').collect();
        if tok.len() < 2 {
            return None;
        }

        Some(IrcMessage {
            sender: sender,
            data: match &message[tok[0].len() .. tok[0].len() + 1] {
                "$" => MessageData::Multiline { lang: tok[0].to_owned(), code: tok[1].to_owned() },
                ">" => MessageData::EvalReq { lang: tok[0].to_owned(), code: tok[1].to_owned(), timeout: true },
                "#" if is_owner => MessageData::EvalReq { lang: tok[0].to_owned(), code: tok[1].to_owned(), timeout: false },
                _ => return None
            }
        })
    } else {
        None
    }
}

fn try_cmd(msg: &str, cmd_prefix: &str, owner: bool) -> Option<MessageData> {
    if let Some((cmd, args)) = parse_cmd(msg, cmd_prefix) {
        match &cmd as &str {
            "quit" if owner => Some(MessageData::Quit),
            "rehash" if owner => Some(MessageData::Rehash),
            "restart" if owner && !args.is_empty() => Some(MessageData::Restart { lang: args[0].to_owned() }),
            "raw" if owner && !args.is_empty() => {
                Some(MessageData::Raw {
                    msg: {
                        let mut m = args.join(" ");
                        m.push_str("\r\n");
                        m
                    }
                })
            },
            "cancel" if !args.is_empty() => Some(MessageData::CancelMultiline { lang: args[0].to_owned() }),
            _ => None,
        }
    } else {
        None
    }
}

fn parse_cmd(msg: &str, cmd_prefix: &str) -> Option<(String, Vec<String>)> {
    let tok: Vec<&str> = match msg.splitn(2, cmd_prefix).nth(1).map(|x| x.split(' ').collect()) {
        Some(x) => x,
        None => return None,
    };

    if tok.is_empty() {
        return None;
    }

    Some((tok[0].to_owned(),
          tok.into_iter().skip(1).map(|x| x.to_owned()).collect::<Vec<String>>()))
}

fn worker(conn: NetIrcServer,
    tx: EvalWorkerSender,
    owners: Vec<String>,
    cmd_prefix: String,
    quit_handle: mpsc::Sender<()>,
    conn_hash: String) {
    'connection: loop {
        println!("connecting to to {:?} as {:?}", conn.config().server, conn.config().nickname);
        while let Err(x) = conn.identify() {
            println!("error while identify()ing; retrying: {:?}", x);
            thread::sleep(Duration::new(1, 0));
        }
        println!("connected to {:?} as {:?}", conn.config().server, conn.config().nickname);
        for msg in conn.iter() {
            let msg = match msg {
                Ok(x) => x,
                Err(_) => continue,
            };

            if let Some(msg) = parse_msg(&conn_hash, &msg, &owners, &cmd_prefix) {
                println!("M: {:?}", msg);
                match msg.data {
                    MessageData::EvalReq { .. } | MessageData::Rehash | MessageData::Restart { .. } | MessageData::Multiline { .. } | MessageData::CancelMultiline { .. } => {
                        let conn = conn.clone();
                        util::ignore(tx.send((msg, Box::new(move |to, r| send_msg!(conn, to, r)))))
                    }
                    MessageData::Raw { msg: raw_msg } => {
                        match raw_msg.parse::<ircp::Message>() {
                            Ok(raw_msg) => {
                                println!("{} sent raw: {:?}", msg.sender.sender, raw_msg);
                                if let Err(err) = conn.send(raw_msg) {
                                    send_notice!(conn, &msg.sender.sender, &format!("error: {:?}", err))
                                }
                            }
                            Err(err) => send_notice!(conn, &msg.sender.sender, &format!("error: {:?}", err)),
                        };
                    }
                    MessageData::Quit => {
                        util::ignore(quit_handle.send(()));
                        break 'connection;
                    }
                }
            }
        }
    }
}

fn eval_worker(mut svc: EvalSvc, rx: EvalWorkerReceiver, ocfg: OutputCfg) {
    let mut mlbufs: HashMap<(MessageSource, String), String> = HashMap::new();
    while let Ok((msg, callback)) = rx.recv() {
        macro_rules! reply {
            ($x:expr) => { callback(msg.sender.reply_to(), $x); }
        }
        macro_rules! key {
            ($x:expr) => { (msg.sender.clone(), $x.clone()) }
        }
        match msg.data {
            MessageData::EvalReq { lang, code, timeout } => {
                let code = match mlbufs.remove(&key!(lang)) {
                    Some(mut buf) => { buf.push_str(&code); buf }
                    None => code
                };
                let ocfg = ocfg.clone();
                let sender = msg.sender.clone();
                svc.eval(&lang,
                         code,
                         timeout,
                         Some(format!("{}{}", msg.sender.conn_hash, msg.sender.reply_to())),
                         Box::new(move |resp| respond(resp, ocfg, sender, callback)))
            }
            MessageData::Rehash => {
                if let Ok(nsvc) = start_evalsvc() {
                    reply!("rehashed");
                    svc = nsvc;
                } else {
                    reply!("read config failed; check logs");
                }
            }
            MessageData::Restart { lang } => {
                match svc.restart(&lang) {
                    Ok(_) => reply!(&format!("restarted {}", lang)),
                    Err(_) => reply!(&format!("no such language {}", lang)),
                }
            }
            MessageData::Multiline { lang, code } => {
                let key = key!(lang);
                if let Some(buf) = mlbufs.get_mut(&key) {
                    buf.push_str(&code);
                    continue;
                }
                mlbufs.insert(key, code);
            }
            MessageData::CancelMultiline { lang } => {
                match mlbufs.remove(&key!(lang)) {
                    Some(_) => reply!(&format!("{}: OK, cleared {} buffer", msg.sender.sender, lang)),
                    None => reply!(&format!("{}: no buffer for {}", msg.sender.sender, lang))
                }
            }
            _ => println!("invalid thing sent to eval_worker?"),
        };
    }
}

fn sanitise_output(input: &str, prefix: Option<&str>, max_len: usize, max_lines: usize) -> Vec<String> {
    let (mut ret, initial_lines): (Vec<String>, usize) = {
        let med = input.lines()
                       .map(|l| l.trim())
                       .filter(|l| !l.is_empty())
                       .flat_map(|l| l.split(util::LengthPattern(max_len)))
                       .collect::<Vec<_>>();

        (if let Some(prefix) = prefix {
            med.iter().take(max_lines).map(|l| prefix.to_owned() + l).collect()
        } else {
            med.iter().take(max_lines).map(|l| (*l).to_owned()).collect()
        },
         med.len())
    };
    if ret.is_empty() {
        ret.push("(ok, no output)".to_owned());
    } else if ret.len() < initial_lines {
        if let Some(ref mut l) = ret.last_mut() {
            l.push_str("... (truncated)");
        }
    }
    ret
}

#[allow(boxed_local)] // send_msg is already boxed anyway
fn respond(resp: Response, cfg: OutputCfg, to: MessageSource, send_msg: SendMsgFnBox) {
    let (response, error) = match resp {
        Response::Success(x) => (x, false),
        Response::Error(x) => (x, true),
        _ => return,
    };
    let output = sanitise_output(&response,
                                 if to.is_chan() && !error { Some(&cfg.chan_output_prefix) } else { None },
                                 if to.is_chan() { cfg.max_channel_line_len } else { 425 },
                                 if to.is_chan() { cfg.max_channel_lines } else { cfg.max_priv_lines });
    for line in &output {
        send_msg(to.reply_to(), line);
    }
}
<|endoftext|>


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

use crate::code_block::CodeBlock;
use crate::errors::{CompilationError, Error};
use json;
use regex::Regex;
use std;
use std::fs;
use std::path::{Path, PathBuf};
use crate::EvalContext;

fn shared_object_name_from_crate_name(crate_name: &str) -> String {
    if cfg!(target_os = "macos") {
        format!("lib{}.dylib", crate_name)
    } else if cfg!(target_os = "windows") {
        format!("{}.dll", crate_name)
    } else {
        format!("lib{}.so", crate_name)
    }
}

fn create_dir(dir: &Path) -> Result<(), Error> {
    if let Err(err) = fs::create_dir_all(dir) {
        bail!("Error creating directory '{:?}': {}", dir, err);
    }
    Ok(())
}

fn write_file(dir: &Path, basename: &str, contents: &str) -> Result<(), Error> {
    create_dir(dir)?;
    let filename = dir.join(basename);
    if let Err(err) = fs::write(&filename, contents) {
        bail!("Error writing '{:?}': {}", filename, err);
    }
    Ok(())
}

pub(crate) struct Module {
    pub(crate) tmpdir: PathBuf,
    build_num: i32,
}

const CRATE_NAME: &str = "ctx";

impl Module {
    pub(crate) fn new(tmpdir: PathBuf) -> Result<Module, Error> {
        let module = Module {
            tmpdir,
            build_num: 0,
        };
        Ok(module)
    }

    pub(crate) fn deps_dir(&self) -> PathBuf {
        self.target_dir().join("debug").join("deps")
    }

    fn target_dir(&self) -> PathBuf {
        self.tmpdir.join("target")
    }

    fn so_path(&self) -> PathBuf {
        self.deps_dir()
            .join(shared_object_name_from_crate_name(CRATE_NAME))
    }

    fn src_dir(&self) -> PathBuf {
        self.tmpdir.join("src")
    }

    pub(crate) fn crate_dir(&self) -> &Path {
        &self.tmpdir
    }

    // Writes Cargo.toml. Should be called before compile.
    pub(crate) fn write_cargo_toml(&self, eval_context: &EvalContext) -> Result<(), Error> {
        write_file(
            self.crate_dir(),
            "Cargo.toml",
            &self.get_cargo_toml_contents(eval_context),
        )
    }

    pub(crate) fn compile(&mut self, code_block: &CodeBlock) -> Result<SoFile, Error> {
        write_file(&self.src_dir(), "lib.rs", &code_block.to_string())?;

        // Our compiler errors should all be in JSON format, but for errors from Cargo errors, we
        // need to add explicit matching for those errors that we expect we might see.
        lazy_static! {
            static ref KNOWN_NON_JSON_ERRORS: Regex =
                Regex::new("(error: no matching package named)").unwrap();
        }

        let mut command = std::process::Command::new("cargo");
        command
            .env("CARGO_TARGET_DIR", &self.target_dir())
            .arg("rustc")
            .arg("--message-format=json")
            .arg("--")
            .arg("-C")
            .arg("prefer-dynamic")
            .current_dir(self.crate_dir());
        let cargo_output = command.output()?;
        if !cargo_output.status.success() {
            let stderr = String::from_utf8_lossy(&cargo_output.stderr);
            let stdout = String::from_utf8_lossy(&cargo_output.stdout);
            let mut non_json_error = None;
            let errors: Vec<CompilationError> = stderr
                .lines()
                .chain(stdout.lines())
                .filter_map(|line| {
                    json::parse(&line)
                        .ok()
                        .and_then(|json| CompilationError::opt_new(json, code_block))
                        .or_else(|| {
                            if KNOWN_NON_JSON_ERRORS.is_match(line) {
                                non_json_error = Some(line);
                            }
                            None
                        })
                })
                .collect();
            if errors.is_empty() {
                if let Some(error) = non_json_error {
                    bail!(Error::Message(error.to_owned()));
                } else {
                    bail!(Error::Message(format!(
                        "Compilation failed, but no parsable errors were found. STDERR:\n\
                         {}\nSTDOUT:{}\n",
                        stderr, stdout
                    )));
                }
            } else {
                bail!(Error::CompilationErrors(errors));
            }
        }
        self.build_num += 1;
        let copied_so_file = self
            .deps_dir()
            .join(shared_object_name_from_crate_name(&format!(
                "code_{}",
                self.build_num
            )));
        // Every time we compile, the output file is the same. We need to copy
        // it so that we have a unique filename, otherwise we wouldn't be able
        // to load the result of the next compilation. Also, on Windows, a
        // loaded dll gets locked, so we couldn't even compile a second time if
        // we didn't load a copy of the file.
        if let Err(err) = fs::copy(self.so_path(), &copied_so_file) {
            bail!(
                "Error copying '{:?}' to '{:?}': {}",
                self.so_path(),
                copied_so_file,
                err
            );
        }
        Ok(SoFile {
            path: copied_so_file,
        })
    }

    fn get_cargo_toml_contents(&self, eval_context: &EvalContext) -> String {
        let crate_imports = eval_context.format_cargo_deps();
        format!(
            r#"
[package]
name = "{}"
version = "1.0.0"
edition = "2018"

[lib]
crate-type = ["dylib"]

[profile.dev]
opt-level = {}
debug = true
rpath = true
lto = false
debug-assertions = true
codegen-units = 16
panic = 'unwind'
incremental = true
overflow-checks = true

[dependencies]
{}
"#,
            CRATE_NAME,
            eval_context.opt_level(),
            crate_imports
        )
    }
}

pub(crate) struct SoFile {
    pub(crate) path: PathBuf,
}
<commit_msg>Rename shared object instead of copying it<commit_after>// Copyright 2018 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use crate::code_block::CodeBlock;
use crate::errors::{CompilationError, Error};
use json;
use regex::Regex;
use std;
use std::fs;
use std::path::{Path, PathBuf};
use crate::EvalContext;

fn shared_object_name_from_crate_name(crate_name: &str) -> String {
    if cfg!(target_os = "macos") {
        format!("lib{}.dylib", crate_name)
    } else if cfg!(target_os = "windows") {
        format!("{}.dll", crate_name)
    } else {
        format!("lib{}.so", crate_name)
    }
}

fn create_dir(dir: &Path) -> Result<(), Error> {
    if let Err(err) = fs::create_dir_all(dir) {
        bail!("Error creating directory '{:?}': {}", dir, err);
    }
    Ok(())
}

fn write_file(dir: &Path, basename: &str, contents: &str) -> Result<(), Error> {
    create_dir(dir)?;
    let filename = dir.join(basename);
    if let Err(err) = fs::write(&filename, contents) {
        bail!("Error writing '{:?}': {}", filename, err);
    }
    Ok(())
}

pub(crate) struct Module {
    pub(crate) tmpdir: PathBuf,
    build_num: i32,
}

const CRATE_NAME: &str = "ctx";

impl Module {
    pub(crate) fn new(tmpdir: PathBuf) -> Result<Module, Error> {
        let module = Module {
            tmpdir,
            build_num: 0,
        };
        Ok(module)
    }

    pub(crate) fn deps_dir(&self) -> PathBuf {
        self.target_dir().join("debug").join("deps")
    }

    fn target_dir(&self) -> PathBuf {
        self.tmpdir.join("target")
    }

    fn so_path(&self) -> PathBuf {
        self.deps_dir()
            .join(shared_object_name_from_crate_name(CRATE_NAME))
    }

    fn src_dir(&self) -> PathBuf {
        self.tmpdir.join("src")
    }

    pub(crate) fn crate_dir(&self) -> &Path {
        &self.tmpdir
    }

    // Writes Cargo.toml. Should be called before compile.
    pub(crate) fn write_cargo_toml(&self, eval_context: &EvalContext) -> Result<(), Error> {
        write_file(
            self.crate_dir(),
            "Cargo.toml",
            &self.get_cargo_toml_contents(eval_context),
        )
    }

    pub(crate) fn compile(&mut self, code_block: &CodeBlock) -> Result<SoFile, Error> {
        write_file(&self.src_dir(), "lib.rs", &code_block.to_string())?;

        // Our compiler errors should all be in JSON format, but for errors from Cargo errors, we
        // need to add explicit matching for those errors that we expect we might see.
        lazy_static! {
            static ref KNOWN_NON_JSON_ERRORS: Regex =
                Regex::new("(error: no matching package named)").unwrap();
        }

        let mut command = std::process::Command::new("cargo");
        command
            .env("CARGO_TARGET_DIR", &self.target_dir())
            .arg("rustc")
            .arg("--message-format=json")
            .arg("--")
            .arg("-C")
            .arg("prefer-dynamic")
            .current_dir(self.crate_dir());
        let cargo_output = command.output()?;
        if !cargo_output.status.success() {
            let stderr = String::from_utf8_lossy(&cargo_output.stderr);
            let stdout = String::from_utf8_lossy(&cargo_output.stdout);
            let mut non_json_error = None;
            let errors: Vec<CompilationError> = stderr
                .lines()
                .chain(stdout.lines())
                .filter_map(|line| {
                    json::parse(&line)
                        .ok()
                        .and_then(|json| CompilationError::opt_new(json, code_block))
                        .or_else(|| {
                            if KNOWN_NON_JSON_ERRORS.is_match(line) {
                                non_json_error = Some(line);
                            }
                            None
                        })
                })
                .collect();
            if errors.is_empty() {
                if let Some(error) = non_json_error {
                    bail!(Error::Message(error.to_owned()));
                } else {
                    bail!(Error::Message(format!(
                        "Compilation failed, but no parsable errors were found. STDERR:\n\
                         {}\nSTDOUT:{}\n",
                        stderr, stdout
                    )));
                }
            } else {
                bail!(Error::CompilationErrors(errors));
            }
        }
        self.build_num += 1;
        let copied_so_file = self
            .deps_dir()
            .join(shared_object_name_from_crate_name(&format!(
                "code_{}",
                self.build_num
            )));
        // Every time we compile, the output file is the same. We need to
        // renamed it so that we have a unique filename, otherwise we wouldn't
        // be able to load the result of the next compilation. Also, on Windows,
        // a loaded dll gets locked, so we couldn't even compile a second time
        // if we didn't load a different file.
        if let Err(err) = fs::rename(self.so_path(), &copied_so_file) {
            bail!(
                "Error copying '{:?}' to '{:?}': {}",
                self.so_path(),
                copied_so_file,
                err
            );
        }
        Ok(SoFile {
            path: copied_so_file,
        })
    }

    fn get_cargo_toml_contents(&self, eval_context: &EvalContext) -> String {
        let crate_imports = eval_context.format_cargo_deps();
        format!(
            r#"
[package]
name = "{}"
version = "1.0.0"
edition = "2018"

[lib]
crate-type = ["dylib"]

[profile.dev]
opt-level = {}
debug = true
rpath = true
lto = false
debug-assertions = true
codegen-units = 16
panic = 'unwind'
incremental = true
overflow-checks = true

[dependencies]
{}
"#,
            CRATE_NAME,
            eval_context.opt_level(),
            crate_imports
        )
    }
}

pub(crate) struct SoFile {
    pub(crate) path: PathBuf,
}
<|endoftext|>


<commit_before>use std::marker::PhantomData;
use std::thread::{sleep, spawn};
use std::{fmt, sync::Arc, time::Duration};

use antidote::RwLock;
use irc::client::prelude::*;

use chrono::{self, NaiveDateTime};
use time;

use plugin::*;
use FrippyClient;

pub mod database;
mod parser;
use self::database::Database;
use self::parser::CommandParser;

use self::error::*;
use error::ErrorKind as FrippyErrorKind;
use error::FrippyError;
use failure::ResultExt;

fn get_time() -> NaiveDateTime {
    let tm = time::now().to_timespec();
    NaiveDateTime::from_timestamp(tm.sec, 0u32)
}

fn get_events<T: Database>(db: &RwLock<T>, in_next: chrono::Duration) -> Vec<database::Event> {
    loop {
        let before = get_time() + in_next;
        match db.read().get_events_before(&before) {
            Ok(events) => return events,
            Err(e) => {
                if e.kind() != ErrorKind::NotFound {
                    error!("Failed to get events: {}", e);
                }
            }
        }

        sleep(in_next.to_std().expect("Failed to convert look ahead time"));
    }
}

fn run<T: Database, C: FrippyClient>(client: &C, db: Arc<RwLock<T>>) {
    let look_ahead = chrono::Duration::minutes(2);

    let mut events = get_events(&db, look_ahead);

    let mut sleep_time = look_ahead
        .to_std()
        .expect("Failed to convert look ahead time");

    loop {
        let now = get_time();
        for event in events {
            if event.time <= now {
                let msg = format!("Reminder from {}: {}", event.author, event.content);
                if let Err(e) = client.send_notice(&event.receiver, &msg) {
                    error!("Failed to send reminder: {}", e);
                } else {
                    debug!("Sent reminder {:?}", event);

                    if let Some(repeat) = event.repeat {
                        let next_time = event.time + chrono::Duration::seconds(repeat);

                        if let Err(e) = db.write().update_event_time(event.id, &next_time) {
                            error!("Failed to update reminder: {}", e);
                        } else {
                            debug!("Updated time");
                        }
                    } else if let Err(e) = db.write().delete_event(event.id) {
                        error!("Failed to delete reminder: {}", e);
                    }
                }
            } else {
                let until_event = (event.time - now)
                    .to_std()
                    .expect("Failed to convert until event time");

                if until_event < sleep_time {
                    sleep_time = until_event + Duration::from_secs(1);
                }
            }
        }

        sleep(sleep_time);
        sleep_time = Duration::from_secs(120);

        events = get_events(&db, look_ahead);
    }
}

#[derive(PluginName)]
pub struct Remind<T: Database + 'static, C> {
    events: Arc<RwLock<T>>,
    has_reminder: RwLock<bool>,
    phantom: PhantomData<C>,
}

impl<T: Database + 'static, C: FrippyClient> Remind<T, C> {
    pub fn new(db: T) -> Self {
        let events = Arc::new(RwLock::new(db));

        Remind {
            events,
            has_reminder: RwLock::new(false),
            phantom: PhantomData,
        }
    }

    fn user_cmd(&self, command: PluginCommand) -> Result<String, RemindError> {
        let parser = CommandParser::parse_target(command.tokens)?;

        self.set(&parser, &command.source)
    }

    fn me_cmd(&self, command: PluginCommand) -> Result<String, RemindError> {
        let source = command.source.clone();
        let parser = CommandParser::with_target(command.tokens, command.source)?;

        self.set(&parser, &source)
    }

    fn set(&self, parser: &CommandParser, author: &str) -> Result<String, RemindError> {
        debug!("parser: {:?}", parser);

        let target = parser.get_target();
        let time = parser.get_time(Duration::from_secs(120))?;

        let event = database::NewEvent {
            receiver: target,
            content: &parser.get_message(),
            author,
            time: &time,
            repeat: parser
                .get_repeat(Duration::from_secs(600))?
                .map(|d| d.as_secs() as i64),
        };

        debug!("New event: {:?}", event);

        Ok(self.events
            .write()
            .insert_event(&event)
            .map(|id| format!("Created reminder with id {} at {} UTC", id, time))?)
    }

    fn list(&self, user: &str) -> Result<String, RemindError> {
        let mut events = self.events.read().get_user_events(user)?;

        if events.is_empty() {
            Err(ErrorKind::NotFound)?;
        }

        let mut list = events.remove(0).to_string();
        for ev in events {
            list.push_str("\r\n");
            list.push_str(&ev.to_string());
        }

        Ok(list)
    }

    fn delete(&self, mut command: PluginCommand) -> Result<&str, RemindError> {
        let id = command
            .tokens
            .remove(0)
            .parse::<i64>()
            .context(ErrorKind::Parsing)?;
        let event = self.events
            .read()
            .get_event(id)
            .context(ErrorKind::NotFound)?;

        if event.receiver.eq_ignore_ascii_case(&command.source)
            || event.author.eq_ignore_ascii_case(&command.source)
        {
            self.events
                .write()
                .delete_event(id)
                .map(|()| "Successfully deleted")
        } else {
            Ok("Only the author or receiver can delete a reminder")
        }
    }

    fn help(&self) -> &str {
        "usage: remind <subcommand>\r\n\
         subcommands: user, me, list, delete, help\r\n\
         examples\r\n\
         remind user foo to sleep in 1 hour\r\n\
         remind me to leave early on 1.1 at 16:00 every week"
    }
}

impl<T: Database, C: FrippyClient + 'static> Plugin for Remind<T, C> {
    type Client = C;
    fn execute(&self, client: &Self::Client, msg: &Message) -> ExecutionStatus {
        if let Command::JOIN(_, _, _) = msg.command {
            let mut has_reminder = self.has_reminder.write();

            if !*has_reminder {
                let events = Arc::clone(&self.events);
                let client = client.clone();

                spawn(move || run(&client, events));

                *has_reminder = true;
            }
        }

        ExecutionStatus::Done
    }

    fn execute_threaded(&self, _: &Self::Client, _: &Message) -> Result<(), FrippyError> {
        panic!("Remind should not use frippy's threading")
    }

    fn command(
        &self,
        client: &Self::Client,
        mut command: PluginCommand,
    ) -> Result<(), FrippyError> {
        if command.tokens.is_empty() {
            client
                .send_notice(&command.source, &ErrorKind::InvalidCommand.to_string())
                .context(FrippyErrorKind::Connection)?;
            return Ok(());
        }

        let source = command.source.clone();

        let sub_command = command.tokens.remove(0);
        let response = match sub_command.as_ref() {
            "user" => self.user_cmd(command),
            "me" => self.me_cmd(command),
            "delete" => self.delete(command).map(|s| s.to_owned()),
            "list" => self.list(&source),
            "help" => Ok(self.help().to_owned()),
            _ => Err(ErrorKind::InvalidCommand.into()),
        };

        match response {
            Ok(msg) => client
                .send_notice(&source, &msg)
                .context(FrippyErrorKind::Connection)?,
            Err(e) => {
                let message = e.to_string();

                client
                    .send_notice(&source, &message)
                    .context(FrippyErrorKind::Connection)?;

                Err(e).context(FrippyErrorKind::Remind)?
            }
        }

        Ok(())
    }

    fn evaluate(&self, _: &Self::Client, _: PluginCommand) -> Result<String, String> {
        Err(String::from(
            "Evaluation of commands is not implemented for remind at this time",
        ))
    }
}

impl<T: Database, C: FrippyClient> fmt::Debug for Remind<T, C> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "Remind {{ ... }}")
    }
}

pub mod error {
    #[derive(Copy, Clone, Eq, PartialEq, Debug, Fail, Error)]
    #[error = "RemindError"]
    pub enum ErrorKind {
        /// Invalid command error
        #[fail(display = "Incorrect Command. Send \"remind help\" for help.")]
        InvalidCommand,

        /// Missing message error
        #[fail(display = "Reminder needs to have a description")]
        MissingMessage,

        /// Missing receiver error
        #[fail(display = "Specify who to remind")]
        MissingReceiver,

        /// Missing time error
        #[fail(display = "Reminder needs to have a time")]
        MissingTime,

        /// Invalid time error
        #[fail(display = "Could not parse time")]
        InvalidTime,

        /// Invalid date error
        #[fail(display = "Could not parse date")]
        InvalidDate,

        /// Parse error
        #[fail(display = "Could not parse integers")]
        Parsing,

        /// Ambigous time error
        #[fail(display = "Time specified is ambiguous")]
        AmbiguousTime,

        /// Time too short error
        #[fail(display = "Reminder needs to be in over 2 minutes")]
        TimeShort,

        /// Repeat time too short error
        #[fail(display = "Repeat time needs to be over 5 minutes")]
        RepeatTimeShort,

        /// Duplicate error
        #[fail(display = "Entry already exists")]
        Duplicate,

        /// Not found error
        #[fail(display = "No events found")]
        NotFound,

        /// MySQL error
        #[cfg(feature = "mysql")]
        #[fail(display = "Failed to execute MySQL Query")]
        MysqlError,

        /// No connection error
        #[cfg(feature = "mysql")]
        #[fail(display = "No connection to the database")]
        NoConnection,
    }
}
<commit_msg>Remind: Fix invalid minimum repeat duration<commit_after>use std::marker::PhantomData;
use std::thread::{sleep, spawn};
use std::{fmt, sync::Arc, time::Duration};

use antidote::RwLock;
use irc::client::prelude::*;

use chrono::{self, NaiveDateTime};
use time;

use plugin::*;
use FrippyClient;

pub mod database;
mod parser;
use self::database::Database;
use self::parser::CommandParser;

use self::error::*;
use error::ErrorKind as FrippyErrorKind;
use error::FrippyError;
use failure::ResultExt;

fn get_time() -> NaiveDateTime {
    let tm = time::now().to_timespec();
    NaiveDateTime::from_timestamp(tm.sec, 0u32)
}

fn get_events<T: Database>(db: &RwLock<T>, in_next: chrono::Duration) -> Vec<database::Event> {
    loop {
        let before = get_time() + in_next;
        match db.read().get_events_before(&before) {
            Ok(events) => return events,
            Err(e) => {
                if e.kind() != ErrorKind::NotFound {
                    error!("Failed to get events: {}", e);
                }
            }
        }

        sleep(in_next.to_std().expect("Failed to convert look ahead time"));
    }
}

fn run<T: Database, C: FrippyClient>(client: &C, db: Arc<RwLock<T>>) {
    let look_ahead = chrono::Duration::minutes(2);

    let mut events = get_events(&db, look_ahead);

    let mut sleep_time = look_ahead
        .to_std()
        .expect("Failed to convert look ahead time");

    loop {
        let now = get_time();
        for event in events {
            if event.time <= now {
                let msg = format!("Reminder from {}: {}", event.author, event.content);
                if let Err(e) = client.send_notice(&event.receiver, &msg) {
                    error!("Failed to send reminder: {}", e);
                } else {
                    debug!("Sent reminder {:?}", event);

                    if let Some(repeat) = event.repeat {
                        let next_time = event.time + chrono::Duration::seconds(repeat);

                        if let Err(e) = db.write().update_event_time(event.id, &next_time) {
                            error!("Failed to update reminder: {}", e);
                        } else {
                            debug!("Updated time");
                        }
                    } else if let Err(e) = db.write().delete_event(event.id) {
                        error!("Failed to delete reminder: {}", e);
                    }
                }
            } else {
                let until_event = (event.time - now)
                    .to_std()
                    .expect("Failed to convert until event time");

                if until_event < sleep_time {
                    sleep_time = until_event + Duration::from_secs(1);
                }
            }
        }

        sleep(sleep_time);
        sleep_time = Duration::from_secs(120);

        events = get_events(&db, look_ahead);
    }
}

#[derive(PluginName)]
pub struct Remind<T: Database + 'static, C> {
    events: Arc<RwLock<T>>,
    has_reminder: RwLock<bool>,
    phantom: PhantomData<C>,
}

impl<T: Database + 'static, C: FrippyClient> Remind<T, C> {
    pub fn new(db: T) -> Self {
        let events = Arc::new(RwLock::new(db));

        Remind {
            events,
            has_reminder: RwLock::new(false),
            phantom: PhantomData,
        }
    }

    fn user_cmd(&self, command: PluginCommand) -> Result<String, RemindError> {
        let parser = CommandParser::parse_target(command.tokens)?;

        self.set(&parser, &command.source)
    }

    fn me_cmd(&self, command: PluginCommand) -> Result<String, RemindError> {
        let source = command.source.clone();
        let parser = CommandParser::with_target(command.tokens, command.source)?;

        self.set(&parser, &source)
    }

    fn set(&self, parser: &CommandParser, author: &str) -> Result<String, RemindError> {
        debug!("parser: {:?}", parser);

        let target = parser.get_target();
        let time = parser.get_time(Duration::from_secs(120))?;

        let event = database::NewEvent {
            receiver: target,
            content: &parser.get_message(),
            author,
            time: &time,
            repeat: parser
                .get_repeat(Duration::from_secs(300))?
                .map(|d| d.as_secs() as i64),
        };

        debug!("New event: {:?}", event);

        Ok(self.events
            .write()
            .insert_event(&event)
            .map(|id| format!("Created reminder with id {} at {} UTC", id, time))?)
    }

    fn list(&self, user: &str) -> Result<String, RemindError> {
        let mut events = self.events.read().get_user_events(user)?;

        if events.is_empty() {
            Err(ErrorKind::NotFound)?;
        }

        let mut list = events.remove(0).to_string();
        for ev in events {
            list.push_str("\r\n");
            list.push_str(&ev.to_string());
        }

        Ok(list)
    }

    fn delete(&self, mut command: PluginCommand) -> Result<&str, RemindError> {
        let id = command
            .tokens
            .remove(0)
            .parse::<i64>()
            .context(ErrorKind::Parsing)?;
        let event = self.events
            .read()
            .get_event(id)
            .context(ErrorKind::NotFound)?;

        if event.receiver.eq_ignore_ascii_case(&command.source)
            || event.author.eq_ignore_ascii_case(&command.source)
        {
            self.events
                .write()
                .delete_event(id)
                .map(|()| "Successfully deleted")
        } else {
            Ok("Only the author or receiver can delete a reminder")
        }
    }

    fn help(&self) -> &str {
        "usage: remind <subcommand>\r\n\
         subcommands: user, me, list, delete, help\r\n\
         examples\r\n\
         remind user foo to sleep in 1 hour\r\n\
         remind me to leave early on 1.1 at 16:00 every week"
    }
}

impl<T: Database, C: FrippyClient + 'static> Plugin for Remind<T, C> {
    type Client = C;
    fn execute(&self, client: &Self::Client, msg: &Message) -> ExecutionStatus {
        if let Command::JOIN(_, _, _) = msg.command {
            let mut has_reminder = self.has_reminder.write();

            if !*has_reminder {
                let events = Arc::clone(&self.events);
                let client = client.clone();

                spawn(move || run(&client, events));

                *has_reminder = true;
            }
        }

        ExecutionStatus::Done
    }

    fn execute_threaded(&self, _: &Self::Client, _: &Message) -> Result<(), FrippyError> {
        panic!("Remind should not use frippy's threading")
    }

    fn command(
        &self,
        client: &Self::Client,
        mut command: PluginCommand,
    ) -> Result<(), FrippyError> {
        if command.tokens.is_empty() {
            client
                .send_notice(&command.source, &ErrorKind::InvalidCommand.to_string())
                .context(FrippyErrorKind::Connection)?;
            return Ok(());
        }

        let source = command.source.clone();

        let sub_command = command.tokens.remove(0);
        let response = match sub_command.as_ref() {
            "user" => self.user_cmd(command),
            "me" => self.me_cmd(command),
            "delete" => self.delete(command).map(|s| s.to_owned()),
            "list" => self.list(&source),
            "help" => Ok(self.help().to_owned()),
            _ => Err(ErrorKind::InvalidCommand.into()),
        };

        match response {
            Ok(msg) => client
                .send_notice(&source, &msg)
                .context(FrippyErrorKind::Connection)?,
            Err(e) => {
                let message = e.to_string();

                client
                    .send_notice(&source, &message)
                    .context(FrippyErrorKind::Connection)?;

                Err(e).context(FrippyErrorKind::Remind)?
            }
        }

        Ok(())
    }

    fn evaluate(&self, _: &Self::Client, _: PluginCommand) -> Result<String, String> {
        Err(String::from(
            "Evaluation of commands is not implemented for remind at this time",
        ))
    }
}

impl<T: Database, C: FrippyClient> fmt::Debug for Remind<T, C> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "Remind {{ ... }}")
    }
}

pub mod error {
    #[derive(Copy, Clone, Eq, PartialEq, Debug, Fail, Error)]
    #[error = "RemindError"]
    pub enum ErrorKind {
        /// Invalid command error
        #[fail(display = "Incorrect Command. Send \"remind help\" for help.")]
        InvalidCommand,

        /// Missing message error
        #[fail(display = "Reminder needs to have a description")]
        MissingMessage,

        /// Missing receiver error
        #[fail(display = "Specify who to remind")]
        MissingReceiver,

        /// Missing time error
        #[fail(display = "Reminder needs to have a time")]
        MissingTime,

        /// Invalid time error
        #[fail(display = "Could not parse time")]
        InvalidTime,

        /// Invalid date error
        #[fail(display = "Could not parse date")]
        InvalidDate,

        /// Parse error
        #[fail(display = "Could not parse integers")]
        Parsing,

        /// Ambigous time error
        #[fail(display = "Time specified is ambiguous")]
        AmbiguousTime,

        /// Time too short error
        #[fail(display = "Reminder needs to be in over 2 minutes")]
        TimeShort,

        /// Repeat time too short error
        #[fail(display = "Repeat time needs to be over 5 minutes")]
        RepeatTimeShort,

        /// Duplicate error
        #[fail(display = "Entry already exists")]
        Duplicate,

        /// Not found error
        #[fail(display = "No events found")]
        NotFound,

        /// MySQL error
        #[cfg(feature = "mysql")]
        #[fail(display = "Failed to execute MySQL Query")]
        MysqlError,

        /// No connection error
        #[cfg(feature = "mysql")]
        #[fail(display = "No connection to the database")]
        NoConnection,
    }
}
<|endoftext|>