<commit_before>use std::collections::HashMap;
use std::rc::Rc;

use rustc_serialize::{self, Decodable, Decoder, json};
use interpolation;

use animation::{Transform, AnimationClip};
use blend_tree::{BlendTreeNode, BlendTreeNodeDef, ClipId};
use math::*;
use skeleton::Skeleton;

const MAX_PARAMS: usize = 16;
const MAX_JOINTS: usize = 64;

pub struct AnimationState {
    blend_tree: BlendTreeNode,
    transitions: Vec<AnimationTransition>,
}

#[derive(Debug, Clone, RustcDecodable)]
pub struct AnimationTransition {
    target_state: String,
    condition: TransitionCondition,
    duration: f32,
}

#[derive(Debug, Clone, RustcDecodable)]
pub struct TransitionCondition {
    parameter: String,
    operator: Operator,
    value: f32,
}

impl TransitionCondition {
    ///
    /// Returns true if the condition is satisfied
    ///
    pub fn is_true(&self, parameters: &HashMap<String, f32>) -> bool {
        match self.operator {
            Operator::LessThan => parameters[&self.parameter[..]] < self.value,
            Operator::GreaterThan => parameters[&self.parameter[..]] > self.value,
            Operator::LessThanEqual => parameters[&self.parameter[..]] <= self.value,
            Operator::GreaterThanEqual => parameters[&self.parameter[..]] >= self.value,
            Operator::Equal => parameters[&self.parameter[..]] == self.value,
            Operator::NotEqual => parameters[&self.parameter[..]] != self.value,
        }
    }
}

#[derive(Debug, Clone)]
pub enum Operator {
    LessThan,
    LessThanEqual,
    GreaterThan,
    GreaterThanEqual,
    Equal,
    NotEqual,
}

impl Decodable for Operator {
    fn decode<D: Decoder>(decoder: &mut D) -> Result<Operator, D::Error> {

        match &try!(decoder.read_str())[..] {
            "<" => Ok(Operator::LessThan),
            ">" => Ok(Operator::GreaterThan),
            "<=" => Ok(Operator::LessThanEqual),
            ">=" => Ok(Operator::GreaterThanEqual),
            "=" => Ok(Operator::Equal),
            "!=" => Ok(Operator::NotEqual),
            _ => Ok(Operator::Equal), // FIXME -- figure out how to throw a D::Error...
        }
    }
}

pub struct AnimationControllerDef {
    parameters: Vec<String>,
    states: Vec<AnimationStateDef>,
    initial_state: String,
}

impl Decodable for AnimationControllerDef {
    fn decode<D: Decoder>(decoder: &mut D) -> Result<AnimationControllerDef, D::Error> {
        decoder.read_struct("root", 0, |decoder| {

            let params = try!(decoder.read_struct_field("parameters", 0, |decoder| {
                decoder.read_seq(|decoder, len| {
                    let mut params = Vec::new();
                    for i in (0 .. len) {
                        params.push(try!(decoder.read_seq_elt(i, Decodable::decode)));
                    }
                    Ok(params)
                })
            }));

            let states = try!(decoder.read_struct_field("states", 0, |decoder| {
                decoder.read_seq(|decoder, len| {
                    let mut states = Vec::new();
                    for i in (0 .. len) {
                        states.push(try!(decoder.read_seq_elt(i, Decodable::decode)));
                    }
                    Ok(states)
                })
            }));

            let initial_state = try!(decoder.read_struct_field("initial_state", 0, |decoder| {
                Ok(try!(decoder.read_str()))
            }));

            Ok(AnimationControllerDef{
                parameters: params,
                states: states,
                initial_state: initial_state,
            })
        })
    }
}

pub struct AnimationStateDef {
    name: String,
    blend_tree: BlendTreeNodeDef,
    transitions: Vec<AnimationTransition>,
}

impl Decodable for AnimationStateDef {
    fn decode<D: Decoder>(decoder: &mut D) -> Result<AnimationStateDef, D::Error> {
        decoder.read_struct("root", 0, |decoder| {

            let name = try!(decoder.read_struct_field("name", 0, |decoder| {
                Ok(try!(decoder.read_str()))
            }));

            let blend_tree = try!(decoder.read_struct_field("blend_tree", 0, Decodable::decode));

            let transitions = try!(decoder.read_struct_field("transitions", 0, |decoder| {
                decoder.read_seq(|decoder, len| {
                    let mut transitions = Vec::new();
                    for i in (0 .. len) {
                        transitions.push(try!(decoder.read_seq_elt(i, Decodable::decode)));
                    }
                    Ok(transitions)
                })
            }));

            Ok(AnimationStateDef {
                name: name,
                blend_tree: blend_tree,
                transitions: transitions,
            })
        })
    }
}

pub struct AnimationController {

    ///
    /// Parameters that will be referenced by blend tree nodes and animation states
    ///
    ///
    ///
    parameters: HashMap<String, f32>,

    ///
    /// Shared reference to the skeleton this controller is using
    ///

    skeleton: Rc<Skeleton>,

    ///
    /// Tracks seconds since controller started running
    ///
    local_clock: f64,

    ///
    /// Playback speed multiplier.
    ///
    playback_speed: f64,

    states: HashMap<String, AnimationState>,
    current_state: String,
    transition: Option<(f64, AnimationTransition)>, // Transition with local clock time of transition start

}

impl AnimationController {

    pub fn new(controller_def: AnimationControllerDef, skeleton: Rc<Skeleton>, animations: &HashMap<ClipId, Rc<AnimationClip>>) -> AnimationController {

        let mut parameters = HashMap::new();

        for parameter in controller_def.parameters.iter() {
            parameters.insert(parameter.clone(), 0.0);
        };

        let mut states = HashMap::new();
        for state_def in controller_def.states.iter() {
            states.insert(state_def.name.clone(), AnimationState {
                blend_tree: BlendTreeNode::from_def(state_def.blend_tree.clone(), animations),
                transitions: state_def.transitions.clone()
            });
        }

        AnimationController {
            parameters: parameters,
            skeleton: skeleton.clone(),
            local_clock: 0.0,
            playback_speed: 1.0,
            states: states,
            current_state: controller_def.initial_state,
            transition: None,
        }
    }

    pub fn update(&mut self, delta_time: f64) {
        self.local_clock += delta_time * self.playback_speed;
    }

    pub fn update_state(&mut self, ext_dt: f64) {

        match self.transition.clone() {

            Some((ref start_time, ref transition)) => {

                // If transition is finished, switch state to new transition
                if self.local_clock + ext_dt >= start_time + transition.duration as f64{
                    self.current_state = transition.target_state.clone();
                    self.transition = None;
                }

            },
            None => {

                // Check for any transitions with passing conditions
                let current_state = &self.states[&self.current_state[..]];
                for transition in current_state.transitions.iter() {

                    if transition.condition.is_true(&self.parameters) {
                        self.transition = Some((self.local_clock + ext_dt, transition.clone()));
                        break;
                    }

                }

            }
        }

    }

    ///
    /// Set the value for the given parameter
    ///
    pub fn set_param_value(&mut self, name: &str, value: f32) {
        self.parameters.insert(name.to_string(), value); // :(
    }

    pub fn set_playback_speed(&mut self, speed: f64) {
        self.playback_speed = speed;
    }

    ///
    /// Return the value for the given parameter
    ///
    pub fn get_param_value(&self, name: &str) -> f32 {
        self.parameters[name]
    }

    ///
    /// Return a read-only reference to the parameter map
    ///
    pub fn get_parameters(&self) -> &HashMap<String, f32> {
        &self.parameters
    }

    ///
    /// Calculate GLOBAL skeletal joint poses for the given time since last update
    ///
    pub fn get_output_pose(&mut self, ext_dt: f64, output_poses: &mut [Matrix4<f32>]) {

        self.update_state(ext_dt);

        let elapsed_time = self.local_clock + ext_dt * self.playback_speed;

        let current_state = &self.states[&self.current_state[..]];

        let mut local_poses = [ Transform {
            translation: [0.0, 0.0, 0.0],
            scale: 0.0,
            rotation: (0.0, [0.0, 0.0, 0.0])
        }; MAX_JOINTS ];

        current_state.blend_tree.get_output_pose(elapsed_time as f32, &self.parameters, &mut local_poses[..]);

        // TODO - would be kinda cool if you could just use a lerp node that pointed to the two
        // blend trees, but then we'd need RC pointers?

        if let Some((transition_start_time, ref transition)) = self.transition {

            // Blend with the target state ...

            let mut target_poses = [ Transform {
                translation: [0.0, 0.0, 0.0],
                scale: 0.0,
                rotation: (0.0, [0.0, 0.0, 0.0])
            }; MAX_JOINTS ];

            let target_state = &self.states[&transition.target_state[..]];

            target_state.blend_tree.get_output_pose(elapsed_time as f32, &self.parameters, &mut target_poses[..]);

            let blend_parameter = ((self.local_clock + ext_dt - transition_start_time) / transition.duration as f64) as f32;

            for i in (0 .. output_poses.len()) {
                let pose_1 = &mut local_poses[i];
                let pose_2 = target_poses[i];
                pose_1.scale = interpolation::lerp(&pose_1.scale, &pose_2.scale, &blend_parameter);
                pose_1.translation = interpolation::lerp(&pose_1.translation, &pose_2.translation, &blend_parameter);
                pose_1.rotation = lerp_quaternion(&pose_1.rotation, &pose_2.rotation, &blend_parameter);
            }

        }

        self.calculate_global_poses(&local_poses[..], output_poses);

    }

    ///
    /// Calculate global poses from the controller's skeleton and the given local poses
    ///
    pub fn calculate_global_poses(
        &self,
        local_poses: &[Transform],
        global_poses: &mut [Matrix4<f32>],
    ) {

        for (joint_index, joint) in self.skeleton.joints.iter().enumerate() {

            let parent_pose = if !joint.is_root() {
                global_poses[joint.parent_index as usize]
            } else {
                mat4_id()
            };

            let local_pose = &local_poses[joint_index];

            let mut local_pose_matrix = quaternion_to_matrix(local_pose.rotation);

            local_pose_matrix[0][3] = local_pose.translation[0];
            local_pose_matrix[1][3] = local_pose.translation[1];
            local_pose_matrix[2][3] = local_pose.translation[2];

            global_poses[joint_index] = row_mat4_mul(parent_pose, local_pose_matrix);
        }
    }
}
<commit_msg>Doc cleanup in controller module<commit_after>use std::collections::HashMap;
use std::rc::Rc;

use rustc_serialize::{Decodable, Decoder};
use interpolation;

use animation::{Transform, AnimationClip};
use blend_tree::{BlendTreeNode, BlendTreeNodeDef, ClipId};
use math::*;
use skeleton::Skeleton;

const MAX_JOINTS: usize = 64;

/// A state that an AnimationController can be in, consisting
/// of a blend tree and a collection of transitions to other states
pub struct AnimationState {

    /// The blend tree used to determine the final blended pose
    /// for this state
    pub blend_tree: BlendTreeNode,

    /// Transitions from this state to other AnimationStates
    pub transitions: Vec<AnimationTransition>,
}

/// Representation of a state transition to a target state, with a condition and a duration
#[derive(Debug, Clone, RustcDecodable)]
pub struct AnimationTransition {
    /// The name of the target state to transition to
    pub target_state: String,

    /// The condition that will be checked in order to determine
    /// if the controller should transition to the target state
    pub condition: TransitionCondition,

    /// The duration of the transition, during which a linear blend
    /// transition between the current and target states should occur
    pub duration: f32,
}

/// Representation of a condition to check for an AnimationTransition
#[derive(Debug, Clone, RustcDecodable)]
pub struct TransitionCondition {

    /// The name of the controller parameter to compare with
    pub parameter: String,

    /// The comparision operator to use
    pub operator: Operator,

    /// The constant value to compare with the controller parameter value
    pub value: f32,
}

impl TransitionCondition {
    /// Returns true if the condition is satisfied
    pub fn is_true(&self, parameters: &HashMap<String, f32>) -> bool {
        match self.operator {
            Operator::LessThan => parameters[&self.parameter[..]] < self.value,
            Operator::GreaterThan => parameters[&self.parameter[..]] > self.value,
            Operator::LessThanEqual => parameters[&self.parameter[..]] <= self.value,
            Operator::GreaterThanEqual => parameters[&self.parameter[..]] >= self.value,
            Operator::Equal => parameters[&self.parameter[..]] == self.value,
            Operator::NotEqual => parameters[&self.parameter[..]] != self.value,
        }
    }
}

#[derive(Debug, Clone)]
pub enum Operator {
    LessThan,
    LessThanEqual,
    GreaterThan,
    GreaterThanEqual,
    Equal,
    NotEqual,
}

impl Decodable for Operator {
    fn decode<D: Decoder>(decoder: &mut D) -> Result<Operator, D::Error> {
        match &try!(decoder.read_str())[..] {
            "<" => Ok(Operator::LessThan),
            ">" => Ok(Operator::GreaterThan),
            "<=" => Ok(Operator::LessThanEqual),
            ">=" => Ok(Operator::GreaterThanEqual),
            "=" => Ok(Operator::Equal),
            "!=" => Ok(Operator::NotEqual),
            _ => Ok(Operator::Equal), // FIXME -- figure out how to throw a D::Error...
        }
    }
}

/// Definition struct for an AnimationController, which can be deserialized from JSON
/// and converted to an AnimationController instance at runtime
pub struct AnimationControllerDef {

    /// Declaration list of all parameters that are used by the AnimationController,
    /// including state transition conditions and blend tree parameters
    pub parameters: Vec<String>,

    /// List of animation state definitions
    pub states: Vec<AnimationStateDef>,

    /// The name of the state that the AnimationController should start in
    pub initial_state: String,
}

impl Decodable for AnimationControllerDef {
    fn decode<D: Decoder>(decoder: &mut D) -> Result<AnimationControllerDef, D::Error> {
        decoder.read_struct("root", 0, |decoder| {

            let params = try!(decoder.read_struct_field("parameters", 0, |decoder| {
                decoder.read_seq(|decoder, len| {
                    let mut params = Vec::new();
                    for i in (0 .. len) {
                        params.push(try!(decoder.read_seq_elt(i, Decodable::decode)));
                    }
                    Ok(params)
                })
            }));

            let states = try!(decoder.read_struct_field("states", 0, |decoder| {
                decoder.read_seq(|decoder, len| {
                    let mut states = Vec::new();
                    for i in (0 .. len) {
                        states.push(try!(decoder.read_seq_elt(i, Decodable::decode)));
                    }
                    Ok(states)
                })
            }));

            let initial_state = try!(decoder.read_struct_field("initial_state", 0, |decoder| {
                Ok(try!(decoder.read_str()))
            }));

            Ok(AnimationControllerDef{
                parameters: params,
                states: states,
                initial_state: initial_state,
            })
        })
    }
}

/// Definition struct for an AnimationState, which can be deserialized from JSON
/// and converted to an AnimationState instance at runtime
pub struct AnimationStateDef {

    /// The identifying name for the state
    pub name: String,

    /// The blend tree definition for this state
    pub blend_tree: BlendTreeNodeDef,

    /// The transitions to other states that can occur from this state
    pub transitions: Vec<AnimationTransition>,
}

impl Decodable for AnimationStateDef {
    fn decode<D: Decoder>(decoder: &mut D) -> Result<AnimationStateDef, D::Error> {
        decoder.read_struct("root", 0, |decoder| {

            let name = try!(decoder.read_struct_field("name", 0, |decoder| {
                Ok(try!(decoder.read_str()))
            }));

            let blend_tree = try!(decoder.read_struct_field("blend_tree", 0, Decodable::decode));

            let transitions = try!(decoder.read_struct_field("transitions", 0, |decoder| {
                decoder.read_seq(|decoder, len| {
                    let mut transitions = Vec::new();
                    for i in (0 .. len) {
                        transitions.push(try!(decoder.read_seq_elt(i, Decodable::decode)));
                    }
                    Ok(transitions)
                })
            }));

            Ok(AnimationStateDef {
                name: name,
                blend_tree: blend_tree,
                transitions: transitions,
            })
        })
    }
}

/// A runtime representation of an Animation State Machine, consisting of one or more
/// AnimationStates connected by AnimationTransitions, where the output animation
/// pose depends on the current state or any active transitions between states.
pub struct AnimationController {

    /// Parameters that will be referenced by blend tree nodes and animation states
    parameters: HashMap<String, f32>,

    /// Shared reference to the skeleton this controller is using
    skeleton: Rc<Skeleton>,

    /// Tracks seconds since controller started running
    local_clock: f64,

    /// Playback speed multiplier.
    playback_speed: f64,

    /// Mapping of all animation state names to their instances
    states: HashMap<String, AnimationState>,

    /// The name of the current active AnimationState
    current_state: String,

    /// The current active AnimationTransition and its start time, if any
    transition: Option<(f64, AnimationTransition)>,
}

impl AnimationController {

    /// Create an AnimationController instance from its definition, the desired skeleton, and a
    /// collection of currently loaded animation clips.
    pub fn new(controller_def: AnimationControllerDef, skeleton: Rc<Skeleton>, animations: &HashMap<ClipId, Rc<AnimationClip>>) -> AnimationController {

        let mut parameters = HashMap::new();

        for parameter in controller_def.parameters.iter() {
            parameters.insert(parameter.clone(), 0.0);
        };

        let mut states = HashMap::new();
        for state_def in controller_def.states.iter() {
            states.insert(state_def.name.clone(), AnimationState {
                blend_tree: BlendTreeNode::from_def(state_def.blend_tree.clone(), animations),
                transitions: state_def.transitions.clone()
            });
        }

        AnimationController {
            parameters: parameters,
            skeleton: skeleton.clone(),
            local_clock: 0.0,
            playback_speed: 1.0,
            states: states,
            current_state: controller_def.initial_state,
            transition: None,
        }
    }

    /// Update the controller's local clock with the given time delta
    pub fn update(&mut self, delta_time: f64) {
        self.local_clock += delta_time * self.playback_speed;
    }

    /// Checks if controller should transition to a different state, or if currently
    /// in a transition, checks if the transition is complete
    fn update_state(&mut self, ext_dt: f64) {
        match self.transition.clone() {
            Some((ref start_time, ref transition)) => {
                // If transition is finished, switch state to new transition
                if self.local_clock + ext_dt >= start_time + transition.duration as f64{
                    self.current_state = transition.target_state.clone();
                    self.transition = None;
                }
            },
            None => {

                // Check for any transitions with passing conditions
                let current_state = &self.states[&self.current_state[..]];
                for transition in current_state.transitions.iter() {

                    if transition.condition.is_true(&self.parameters) {
                        self.transition = Some((self.local_clock + ext_dt, transition.clone()));
                        break;
                    }
                }
            }
        }
    }

    /// Set the playback speed for the controller
    pub fn set_playback_speed(&mut self, speed: f64) {
        self.playback_speed = speed;
    }

    /// Set the value for the given controller parameter
    pub fn set_param_value(&mut self, name: &str, value: f32) {
        self.parameters.insert(name.to_string(), value); // :(
    }

    /// Return the value for the given controller parameter
    pub fn get_param_value(&self, name: &str) -> f32 {
        self.parameters[name]
    }

    /// Return a read-only reference to the controller parameter map
    pub fn get_parameters(&self) -> &HashMap<String, f32> {
        &self.parameters
    }

    /// Calculate global skeletal joint poses for the given time since last update
    pub fn get_output_pose(&mut self, ext_dt: f64, output_poses: &mut [Matrix4<f32>]) {

        self.update_state(ext_dt);

        let elapsed_time = self.local_clock + ext_dt * self.playback_speed;

        let current_state = &self.states[&self.current_state[..]];

        let mut local_poses = [ Transform {
            translation: [0.0, 0.0, 0.0],
            scale: 0.0,
            rotation: (0.0, [0.0, 0.0, 0.0])
        }; MAX_JOINTS ];

        current_state.blend_tree.get_output_pose(elapsed_time as f32, &self.parameters, &mut local_poses[..]);

        // TODO - would be kinda cool if you could just use a lerp node that pointed to the two
        // blend trees, but then we'd need RC pointers?

        if let Some((transition_start_time, ref transition)) = self.transition {

            // Blend with the target state ...

            let mut target_poses = [ Transform {
                translation: [0.0, 0.0, 0.0],
                scale: 0.0,
                rotation: (0.0, [0.0, 0.0, 0.0])
            }; MAX_JOINTS ];

            let target_state = &self.states[&transition.target_state[..]];

            target_state.blend_tree.get_output_pose(elapsed_time as f32, &self.parameters, &mut target_poses[..]);

            let blend_parameter = ((self.local_clock + ext_dt - transition_start_time) / transition.duration as f64) as f32;

            for i in (0 .. output_poses.len()) {
                let pose_1 = &mut local_poses[i];
                let pose_2 = target_poses[i];
                pose_1.scale = interpolation::lerp(&pose_1.scale, &pose_2.scale, &blend_parameter);
                pose_1.translation = interpolation::lerp(&pose_1.translation, &pose_2.translation, &blend_parameter);
                pose_1.rotation = lerp_quaternion(&pose_1.rotation, &pose_2.rotation, &blend_parameter);
            }

        }

        self.calculate_global_poses(&local_poses[..], output_poses);
    }

    /// Calculate global poses from the controller's skeleton and the given local poses
    fn calculate_global_poses(
        &self,
        local_poses: &[Transform],
        global_poses: &mut [Matrix4<f32>],
    ) {

        for (joint_index, joint) in self.skeleton.joints.iter().enumerate() {

            let parent_pose = if !joint.is_root() {
                global_poses[joint.parent_index as usize]
            } else {
                mat4_id()
            };

            let local_pose = &local_poses[joint_index];

            let mut local_pose_matrix = quaternion_to_matrix(local_pose.rotation);

            local_pose_matrix[0][3] = local_pose.translation[0];
            local_pose_matrix[1][3] = local_pose.translation[1];
            local_pose_matrix[2][3] = local_pose.translation[2];

            global_poses[joint_index] = row_mat4_mul(parent_pose, local_pose_matrix);
        }
    }
}
<|endoftext|>


<commit_before>//! Cipher defined with Miscreant

use std::ptr;

use miscreant::aead::{Aes128PmacSiv, Aes256PmacSiv, Algorithm};

use crate::crypto::{
    aead::{increase_nonce, make_skey},
    cipher::Error,
    AeadDecryptor,
    AeadEncryptor,
    CipherResult,
    CipherType,
};

use bytes::{BufMut, BytesMut};

use byte_string::ByteStr;

/// AEAD ciphers provided by Miscreant
pub enum MiscreantCryptoVariant {
    Aes128(Aes128PmacSiv),
    Aes256(Aes256PmacSiv),
}

/// AEAD Cipher context
///
/// According to SIP004, the `nonce` has to incr 1 after each encrypt/decrypt.
pub struct MiscreantCipher {
    cipher_type: CipherType,
    cipher: MiscreantCryptoVariant,
    nonce: BytesMut,
}

impl MiscreantCipher {
    /// Initialize context
    pub fn new(t: CipherType, key: &[u8], salt: &[u8]) -> Self {
        // NOTE: Don't need check salt is duplicated. :)

        let nonce_size = t.iv_size();
        let mut nonce = BytesMut::with_capacity(nonce_size);
        unsafe {
            nonce.set_len(nonce_size);
            ptr::write_bytes(nonce.as_mut_ptr(), 0, nonce_size);
        }

        let skey = make_skey(t, key, salt);
        let cipher = Self::new_variant(t, &skey);
        MiscreantCipher {
            cipher_type: t,
            cipher: cipher,
            nonce: nonce,
        }
    }

    fn new_variant(t: CipherType, key: &[u8]) -> MiscreantCryptoVariant {
        match t {
            CipherType::Aes128PmacSiv => {
                let mut skey = [0; 32];
                skey.copy_from_slice(key);
                MiscreantCryptoVariant::Aes128(Aes128PmacSiv::new(&skey))
            }
            CipherType::Aes256PmacSiv => {
                let mut skey = [0; 64];
                skey.copy_from_slice(key);
                MiscreantCryptoVariant::Aes256(Aes256PmacSiv::new(&skey))
            }
            _ => panic!("unsupported cipher in miscreant {:?}", t),
        }
    }
}

impl AeadEncryptor for MiscreantCipher {
    fn encrypt(&mut self, input: &[u8], output: &mut [u8]) {
        let tag_len = self.cipher_type.tag_size();
        let buf_len = input.len() + tag_len;

        // Miscreant requires tag to be in front of text
        let mut buf = BytesMut::with_capacity(buf_len);
        unsafe {
            buf.set_len(buf_len);
        }
        buf[tag_len..].copy_from_slice(input);

        // NOTE: Must swap tag and encrypted text to output
        match self.cipher {
            MiscreantCryptoVariant::Aes128(ref mut cipher) => {
                cipher.seal_in_place(&self.nonce, b"", &mut buf);
                output[..input.len()].copy_from_slice(&buf[tag_len..]);
                output[input.len()..].copy_from_slice(&buf[..tag_len]);
            }
            MiscreantCryptoVariant::Aes256(ref mut cipher) => {
                cipher.seal_in_place(&self.nonce, b"", &mut buf);
                output[..input.len()].copy_from_slice(&buf[tag_len..]);
                output[input.len()..].copy_from_slice(&buf[..tag_len]);
            }
        }

        increase_nonce(&mut self.nonce);
    }
}

impl AeadDecryptor for MiscreantCipher {
    fn decrypt(&mut self, input: &[u8], output: &mut [u8]) -> CipherResult<()> {
        let tag_size = self.cipher_type.tag_size();

        // Swap encrypted text and tag
        // Miscreant requires tag in front of encrypted text
        let mut buf = BytesMut::with_capacity(input.len() + tag_size);
        buf.put_slice(&input[input.len() - tag_size..]);
        buf.put_slice(&input[..input.len() - tag_size]);

        let result = match self.cipher {
            MiscreantCryptoVariant::Aes128(ref mut cipher) => cipher.open_in_place(&self.nonce, b"", &mut buf),
            MiscreantCryptoVariant::Aes256(ref mut cipher) => cipher.open_in_place(&self.nonce, b"", &mut buf),
        };

        result
            .map(|buf| {
                output.copy_from_slice(buf);
                increase_nonce(&mut self.nonce);
            })
            .map_err(|_| {
                error!(
                    "AEAD decrypt failed, nonce={:?}, input={:?}, tag={:?}, err: decrypt failure",
                    ByteStr::new(&self.nonce),
                    ByteStr::new(&input[..input.len() - tag_size]),
                    ByteStr::new(&input[input.len() - tag_size..])
                );
                Error::AeadDecryptFailed
            })
    }
}

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

    fn test_miscreant(ct: CipherType) {
        let key = ct.bytes_to_key(b"PassWORD");
        let message = b"message";

        let iv = ct.gen_init_vec();

        let mut enc = MiscreantCipher::new(ct, &key[..], &iv[..]);

        let mut encrypted_msg = vec![0u8; message.len() + ct.tag_size()];
        enc.encrypt(message, &mut encrypted_msg);

        assert_ne!(message, &encrypted_msg[..]);

        let mut dec = MiscreantCipher::new(ct, &key[..], &iv[..]);
        let mut decrypted_msg = vec![0u8; message.len()];
        dec.decrypt(&encrypted_msg[..], &mut decrypted_msg).unwrap();

        assert_eq!(&decrypted_msg[..], message);
    }

    #[test]
    fn test_rust_crypto_cipher_aes_128_pmac_siv() {
        test_miscreant(CipherType::Aes128PmacSiv);
    }

    #[test]
    fn test_rust_crypto_cipher_aes_256_pmac_siv() {
        test_miscreant(CipherType::Aes256PmacSiv);
    }
}
<commit_msg>Built with miscreant 0.4.2<commit_after>//! Cipher defined with Miscreant

use std::ptr;

use miscreant::aead::{Aes128PmacSivAead, Aes256PmacSivAead, Aead};

use crate::crypto::{
    aead::{increase_nonce, make_skey},
    cipher::Error,
    AeadDecryptor,
    AeadEncryptor,
    CipherResult,
    CipherType,
};

use bytes::{BufMut, BytesMut};

use byte_string::ByteStr;

/// AEAD ciphers provided by Miscreant
pub enum MiscreantCryptoVariant {
    Aes128(Aes128PmacSivAead),
    Aes256(Aes256PmacSivAead),
}

/// AEAD Cipher context
///
/// According to SIP004, the `nonce` has to incr 1 after each encrypt/decrypt.
pub struct MiscreantCipher {
    cipher_type: CipherType,
    cipher: MiscreantCryptoVariant,
    nonce: BytesMut,
}

impl MiscreantCipher {
    /// Initialize context
    pub fn new(t: CipherType, key: &[u8], salt: &[u8]) -> Self {
        // NOTE: Don't need check salt is duplicated. :)

        let nonce_size = t.iv_size();
        let mut nonce = BytesMut::with_capacity(nonce_size);
        unsafe {
            nonce.set_len(nonce_size);
            ptr::write_bytes(nonce.as_mut_ptr(), 0, nonce_size);
        }

        let skey = make_skey(t, key, salt);
        let cipher = Self::new_variant(t, &skey);
        MiscreantCipher {
            cipher_type: t,
            cipher: cipher,
            nonce: nonce,
        }
    }

    fn new_variant(t: CipherType, key: &[u8]) -> MiscreantCryptoVariant {
        match t {
            CipherType::Aes128PmacSiv => {
                let mut skey = [0; 32];
                skey.copy_from_slice(key);
                MiscreantCryptoVariant::Aes128(Aes128PmacSivAead::new(&skey))
            }
            CipherType::Aes256PmacSiv => {
                let mut skey = [0; 64];
                skey.copy_from_slice(key);
                MiscreantCryptoVariant::Aes256(Aes256PmacSivAead::new(&skey))
            }
            _ => panic!("unsupported cipher in miscreant {:?}", t),
        }
    }
}

impl AeadEncryptor for MiscreantCipher {
    fn encrypt(&mut self, input: &[u8], output: &mut [u8]) {
        let tag_len = self.cipher_type.tag_size();
        let buf_len = input.len() + tag_len;

        // Miscreant requires tag to be in front of text
        let mut buf = BytesMut::with_capacity(buf_len);
        unsafe {
            buf.set_len(buf_len);
        }
        buf[tag_len..].copy_from_slice(input);

        // NOTE: Must swap tag and encrypted text to output
        match self.cipher {
            MiscreantCryptoVariant::Aes128(ref mut cipher) => {
                cipher.seal_in_place(&self.nonce, b"", &mut buf);
                output[..input.len()].copy_from_slice(&buf[tag_len..]);
                output[input.len()..].copy_from_slice(&buf[..tag_len]);
            }
            MiscreantCryptoVariant::Aes256(ref mut cipher) => {
                cipher.seal_in_place(&self.nonce, b"", &mut buf);
                output[..input.len()].copy_from_slice(&buf[tag_len..]);
                output[input.len()..].copy_from_slice(&buf[..tag_len]);
            }
        }

        increase_nonce(&mut self.nonce);
    }
}

impl AeadDecryptor for MiscreantCipher {
    fn decrypt(&mut self, input: &[u8], output: &mut [u8]) -> CipherResult<()> {
        let tag_size = self.cipher_type.tag_size();

        // Swap encrypted text and tag
        // Miscreant requires tag in front of encrypted text
        let mut buf = BytesMut::with_capacity(input.len() + tag_size);
        buf.put_slice(&input[input.len() - tag_size..]);
        buf.put_slice(&input[..input.len() - tag_size]);

        let result = match self.cipher {
            MiscreantCryptoVariant::Aes128(ref mut cipher) => cipher.open_in_place(&self.nonce, b"", &mut buf),
            MiscreantCryptoVariant::Aes256(ref mut cipher) => cipher.open_in_place(&self.nonce, b"", &mut buf),
        };

        result
            .map(|buf| {
                output.copy_from_slice(buf);
                increase_nonce(&mut self.nonce);
            })
            .map_err(|_| {
                error!(
                    "AEAD decrypt failed, nonce={:?}, input={:?}, tag={:?}, err: decrypt failure",
                    ByteStr::new(&self.nonce),
                    ByteStr::new(&input[..input.len() - tag_size]),
                    ByteStr::new(&input[input.len() - tag_size..])
                );
                Error::AeadDecryptFailed
            })
    }
}

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

    fn test_miscreant(ct: CipherType) {
        let key = ct.bytes_to_key(b"PassWORD");
        let message = b"message";

        let iv = ct.gen_init_vec();

        let mut enc = MiscreantCipher::new(ct, &key[..], &iv[..]);

        let mut encrypted_msg = vec![0u8; message.len() + ct.tag_size()];
        enc.encrypt(message, &mut encrypted_msg);

        assert_ne!(message, &encrypted_msg[..]);

        let mut dec = MiscreantCipher::new(ct, &key[..], &iv[..]);
        let mut decrypted_msg = vec![0u8; message.len()];
        dec.decrypt(&encrypted_msg[..], &mut decrypted_msg).unwrap();

        assert_eq!(&decrypted_msg[..], message);
    }

    #[test]
    fn test_rust_crypto_cipher_aes_128_pmac_siv() {
        test_miscreant(CipherType::Aes128PmacSiv);
    }

    #[test]
    fn test_rust_crypto_cipher_aes_256_pmac_siv() {
        test_miscreant(CipherType::Aes256PmacSiv);
    }
}
<|endoftext|>


<commit_before>use std::borrow::Cow;
use std::fmt::{self, Debug, Display};
use std::slice;
use std::str::{self, Utf8Error};

extern "C" {
    #[link_name = "cxxbridge02$cxx_string$data"]
    fn string_data(_: &CxxString) -> *const u8;
    #[link_name = "cxxbridge02$cxx_string$length"]
    fn string_length(_: &CxxString) -> usize;
}

/// Binding to C++ `std::string`.
///
/// # Invariants
///
/// As an invariant of this API and the static analysis of the cxx::bridge
/// macro, in Rust code we can never obtain a `CxxString` by value. C++'s string
/// requires a move constructor and may hold internal pointers, which is not
/// compatible with Rust's move behavior. Instead in Rust code we will only ever
/// look at a CxxString through a reference or smart pointer, as in `&CxxString`
/// or `UniquePtr<CxxString>`.
#[repr(C)]
pub struct CxxString {
    _private: [u8; 0],
}

impl CxxString {
    /// Returns the length of the string in bytes.
    ///
    /// Matches the behavior of C++ [std::string::size][size].
    ///
    /// [size]: https://en.cppreference.com/w/cpp/string/basic_string/size
    pub fn len(&self) -> usize {
        unsafe { string_length(self) }
    }

    /// Returns true if `self` has a length of zero bytes.
    ///
    /// Matches the behavior of C++ [std::string::empty][empty].
    ///
    /// [empty]: https://en.cppreference.com/w/cpp/string/basic_string/empty
    pub fn is_empty(&self) -> bool {
        self.len() == 0
    }

    /// Returns a byte slice of this string's contents.
    pub fn as_bytes(&self) -> &[u8] {
        let data = self.as_ptr();
        let len = self.len();
        unsafe { slice::from_raw_parts(data, len) }
    }

    /// Produces a pointer to the first character of the string.
    ///
    /// Matches the behavior of C++ [std::string::data][data].
    ///
    /// Note that the return type may look like `const char *` but is not a
    /// `const char *` in the typical C sense, as C++ strings may contain
    /// internal null bytes. As such, the returned pointer only makes sense as a
    /// string in combination with the length returned by [`len()`](#len).
    ///
    /// [data]: https://en.cppreference.com/w/cpp/string/basic_string/data
    pub fn as_ptr(&self) -> *const u8 {
        unsafe { string_data(self) }
    }

    /// Validates that the C++ string contains UTF-8 data and produces a view of
    /// it as a Rust &str, otherwise an error.
    pub fn to_str(&self) -> Result<&str, Utf8Error> {
        str::from_utf8(self.as_bytes())
    }

    /// If the contents of the C++ string are valid UTF-8, this function returns
    /// a view as a Cow::Borrowed &str. Otherwise replaces any invalid UTF-8
    /// sequences with the U+FFFD [replacement character] and returns a
    /// Cow::Owned String.
    ///
    /// [replacement character]: https://doc.rust-lang.org/std/char/constant.REPLACEMENT_CHARACTER.html
    pub fn to_string_lossy(&self) -> Cow<str> {
        String::from_utf8_lossy(self.as_bytes())
    }
}

impl Display for CxxString {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        Display::fmt(self.to_string_lossy().as_ref(), f)
    }
}

impl Debug for CxxString {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        Debug::fmt(self.to_string_lossy().as_ref(), f)
    }
}

impl PartialEq for CxxString {
    fn eq(&self, other: &CxxString) -> bool {
        self.as_bytes() == other.as_bytes()
    }
}

impl PartialEq<CxxString> for str {
    fn eq(&self, other: &CxxString) -> bool {
        self.as_bytes() == other.as_bytes()
    }
}

impl PartialEq<str> for CxxString {
    fn eq(&self, other: &str) -> bool {
        self.as_bytes() == other.as_bytes()
    }
}
<commit_msg>Fix link from CxxString::as_ptr to len method<commit_after>use std::borrow::Cow;
use std::fmt::{self, Debug, Display};
use std::slice;
use std::str::{self, Utf8Error};

extern "C" {
    #[link_name = "cxxbridge02$cxx_string$data"]
    fn string_data(_: &CxxString) -> *const u8;
    #[link_name = "cxxbridge02$cxx_string$length"]
    fn string_length(_: &CxxString) -> usize;
}

/// Binding to C++ `std::string`.
///
/// # Invariants
///
/// As an invariant of this API and the static analysis of the cxx::bridge
/// macro, in Rust code we can never obtain a `CxxString` by value. C++'s string
/// requires a move constructor and may hold internal pointers, which is not
/// compatible with Rust's move behavior. Instead in Rust code we will only ever
/// look at a CxxString through a reference or smart pointer, as in `&CxxString`
/// or `UniquePtr<CxxString>`.
#[repr(C)]
pub struct CxxString {
    _private: [u8; 0],
}

impl CxxString {
    /// Returns the length of the string in bytes.
    ///
    /// Matches the behavior of C++ [std::string::size][size].
    ///
    /// [size]: https://en.cppreference.com/w/cpp/string/basic_string/size
    pub fn len(&self) -> usize {
        unsafe { string_length(self) }
    }

    /// Returns true if `self` has a length of zero bytes.
    ///
    /// Matches the behavior of C++ [std::string::empty][empty].
    ///
    /// [empty]: https://en.cppreference.com/w/cpp/string/basic_string/empty
    pub fn is_empty(&self) -> bool {
        self.len() == 0
    }

    /// Returns a byte slice of this string's contents.
    pub fn as_bytes(&self) -> &[u8] {
        let data = self.as_ptr();
        let len = self.len();
        unsafe { slice::from_raw_parts(data, len) }
    }

    /// Produces a pointer to the first character of the string.
    ///
    /// Matches the behavior of C++ [std::string::data][data].
    ///
    /// Note that the return type may look like `const char *` but is not a
    /// `const char *` in the typical C sense, as C++ strings may contain
    /// internal null bytes. As such, the returned pointer only makes sense as a
    /// string in combination with the length returned by [`len()`][len].
    ///
    /// [data]: https://en.cppreference.com/w/cpp/string/basic_string/data
    /// [len]: #method.len
    pub fn as_ptr(&self) -> *const u8 {
        unsafe { string_data(self) }
    }

    /// Validates that the C++ string contains UTF-8 data and produces a view of
    /// it as a Rust &str, otherwise an error.
    pub fn to_str(&self) -> Result<&str, Utf8Error> {
        str::from_utf8(self.as_bytes())
    }

    /// If the contents of the C++ string are valid UTF-8, this function returns
    /// a view as a Cow::Borrowed &str. Otherwise replaces any invalid UTF-8
    /// sequences with the U+FFFD [replacement character] and returns a
    /// Cow::Owned String.
    ///
    /// [replacement character]: https://doc.rust-lang.org/std/char/constant.REPLACEMENT_CHARACTER.html
    pub fn to_string_lossy(&self) -> Cow<str> {
        String::from_utf8_lossy(self.as_bytes())
    }
}

impl Display for CxxString {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        Display::fmt(self.to_string_lossy().as_ref(), f)
    }
}

impl Debug for CxxString {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        Debug::fmt(self.to_string_lossy().as_ref(), f)
    }
}

impl PartialEq for CxxString {
    fn eq(&self, other: &CxxString) -> bool {
        self.as_bytes() == other.as_bytes()
    }
}

impl PartialEq<CxxString> for str {
    fn eq(&self, other: &CxxString) -> bool {
        self.as_bytes() == other.as_bytes()
    }
}

impl PartialEq<str> for CxxString {
    fn eq(&self, other: &str) -> bool {
        self.as_bytes() == other.as_bytes()
    }
}
<|endoftext|>


<commit_before>extern crate rand;
use crate::image::CONF_LINE_IDENTIFIER__IMAGE;
use rand::Rng;

// Config file syntax error message
pub const CONF_SYNTAX_ERROR: &str = "

SYNTAX ERROR in config file!
Every line has to start with one of the following characters:
'#' (comment line), '-' (guessing string), '|' (ASCII-Art image) or ':' (game modifier).
Edit config file and start again.\n";

// comments in config file start with
pub const CONF_LINE_IDENTIFIER__COMMENT: char = '#';

// comands in config-file start with
pub const CONF_LINE_IDENTIFIER__CONTROL: char = ':';

// guessing strings in config-file start with
pub const CONF_LINE_IDENTIFIER__WORD: char = '-';

// a modifier tagging parts of the string to be visible from the start, e.g.
// "guess*-me*: will be shown as "_ _ _ _ _ - m e"
pub const CONF_LINE_WORD_MODIFIER__VISIBLE: char = '*';

#[derive(Debug)]
pub struct Dict {
    wordlist: Vec<String>,
}

impl Dict {
    pub fn len(&self) -> usize {
        self.wordlist.len()
    }

    pub fn new(lines: &str) -> Self {
        Self{wordlist :
          // remove Unicode BOM if present (\u{feff} has in UTF8 3 bytes).
          if lines.starts_with('\u{feff}') { &lines[3..] } else { &lines[..] }
            .lines()
            .enumerate()
            .filter(|&(_,l)|!( l.trim().len() == 0 ||
                          l.starts_with(CONF_LINE_IDENTIFIER__COMMENT) ||
                          l.starts_with(CONF_LINE_IDENTIFIER__CONTROL) ||
                          l.starts_with(CONF_LINE_IDENTIFIER__IMAGE)
                        )
            )
            .map(|(n,l)| if l.starts_with(CONF_LINE_IDENTIFIER__WORD) {
                             l[1..].trim().to_string()
                         } else {
                             panic!("{}\nError in line: {}: \"{}\"\n\n",
                                     CONF_SYNTAX_ERROR, n+1, l)
                     })
            .collect()
        }
    }

    pub fn get_random_word(&self) -> String {
        (*rand::thread_rng().choose(&self.wordlist).unwrap()).to_string()
    }
}
<commit_msg>is_empty() is clearer<commit_after>#![allow(clippy::filter_map)]
extern crate rand;
use crate::image::CONF_LINE_IDENTIFIER__IMAGE;
use rand::Rng;

// Config file syntax error message
pub const CONF_SYNTAX_ERROR: &str = "

SYNTAX ERROR in config file!
Every line has to start with one of the following characters:
'#' (comment line), '-' (guessing string), '|' (ASCII-Art image) or ':' (game modifier).
Edit config file and start again.\n";

// comments in config file start with
pub const CONF_LINE_IDENTIFIER__COMMENT: char = '#';

// comands in config-file start with
pub const CONF_LINE_IDENTIFIER__CONTROL: char = ':';

// guessing strings in config-file start with
pub const CONF_LINE_IDENTIFIER__WORD: char = '-';

// a modifier tagging parts of the string to be visible from the start, e.g.
// "guess*-me*: will be shown as "_ _ _ _ _ - m e"
pub const CONF_LINE_WORD_MODIFIER__VISIBLE: char = '*';

#[derive(Debug)]
pub struct Dict {
    wordlist: Vec<String>,
}

impl Dict {
    pub fn len(&self) -> usize {
        self.wordlist.len()
    }
    pub fn new(lines: &str) -> Self {
        Self{wordlist :
          // remove Unicode BOM if present (\u{feff} has in UTF8 3 bytes).
          if lines.starts_with('\u{feff}') { &lines[3..] } else { &lines[..] }
            .lines()
            .enumerate()
            .filter(|&(_,l)|!( l.trim().is_empty() ||
                          l.starts_with(CONF_LINE_IDENTIFIER__COMMENT) ||
                          l.starts_with(CONF_LINE_IDENTIFIER__CONTROL) ||
                          l.starts_with(CONF_LINE_IDENTIFIER__IMAGE)
                        )
            )
            .map(|(n,l)| if l.starts_with(CONF_LINE_IDENTIFIER__WORD) {
                             l[1..].trim().to_string()
                         } else {
                             panic!("{}\nError in line: {}: \"{}\"\n\n",
                                     CONF_SYNTAX_ERROR, n+1, l)
                     })
            .collect()
        }
    }

    pub fn get_random_word(&self) -> String {
        (*rand::thread_rng().choose(&self.wordlist).unwrap()).to_string()
    }
}
<|endoftext|>


<commit_before>use std::sync::mpsc::{Receiver, Sender};
use std::rc::Rc;

use action::ExecResult;
use super::{config, Condition, Context, Message, TimerEvent};
use reactor::{self, Event, EventDemultiplexer, EventHandler, Reactor};

#[derive(Debug)]
pub enum Request {
    Event(super::Event),
    Exit
}

#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub enum RequestHandler {
    Event,
    Exit
}

impl reactor::Event for Request {
    type Handler = RequestHandler;
    fn handler(&self) -> Self::Handler {
        match *self {
            Request::Event(_) => RequestHandler::Event,
            Request::Exit => RequestHandler::Exit,
        }
    }
}

#[derive(Debug)]
pub enum Response {
    Event(ExecResult),
    Exit
}

pub struct Dispatcher {
    contexts: Vec<Context>,
    output_channel: Sender<Response>,
    exits_received: u32
}

impl Dispatcher {
    pub fn new(contexts: Vec<config::Context>, action_output_channel: Sender<Response>) -> Dispatcher {
        let contexts = contexts.into_iter().map(|ctx| Context::from(ctx)).collect::<Vec<Context>>();
        Dispatcher {
            contexts: contexts,
            output_channel: action_output_channel,
            exits_received: 0
        }
    }

    pub fn start_loop(&mut self, channel: Receiver<Request>) {
        for i in channel.iter() {
            match i {
                Request::Event(event) => self.dispatch(event),
                Request::Exit => {
                    if self.on_exit() {
                        break;
                    }
                }
            }
        }
    }

    fn on_exit(&mut self) -> bool {
        self.exits_received += 1;
        let _ = self.output_channel.send(Response::Exit);
        self.exits_received >= 2
    }

    pub fn dispatch(&mut self, event: super::Event) {
        match event {
            super::Event::Message(event) => {
                let event = Rc::new(event);
                self.on_message(event);
            },
            super::Event::Timer(ref event) => {
                self.on_timer(event);
            }
        };
    }

    fn on_message(&mut self, event: Rc<Message>) {
        for context in self.contexts.iter_mut() {
            if let Some(result) = context.on_message(event.clone()) {
                for i in result.into_iter() {
                    let _ = self.output_channel.send(i.into());
                }
            }
        }
    }

    fn on_timer(&mut self, event: &TimerEvent) {
        for context in self.contexts.iter_mut() {
            if let Some(result) = context.on_timer(event) {
                for i in result.into_iter() {
                    let _ = self.output_channel.send(i.into());
                }
            }
        }
    }
}

use std::collections::BTreeMap;

pub struct RequestReactor {
    handlers: BTreeMap<RequestHandler, Box<reactor::EventHandler<Request, Handler=RequestHandler>>>,
    demultiplexer: Demultiplexer<Request>,
    exit_condition: Condition
}

impl RequestReactor {
    fn new(demultiplexer: Demultiplexer<Request>) -> RequestReactor {
        let exit_condition = Condition::new(false);
        let exit_handler = Box::new(handlers::exit::ExitHandler::new(exit_condition.clone()));
        let event_handler = Box::new(handlers::event::EventHandler::new());

        let mut reactor = RequestReactor {
            demultiplexer: demultiplexer,
            exit_condition: exit_condition,
            handlers: BTreeMap::new()
        };

        reactor.register_handler(exit_handler);
        reactor.register_handler(event_handler);
        reactor
    }
}

impl Reactor for RequestReactor {
    type Event = Request;
    type Handler = RequestHandler;
    fn handle_events(&mut self) {
        while !self.exit_condition.is_active() {
            if let Some(request) = self.demultiplexer.select() {
                let mut handler = self.handlers.get_mut(&request.handler()).unwrap();
                handler.handle_event(request);
            } else {
                break;
            }
        }
    }
    fn register_handler(&mut self, handler: Box<reactor::EventHandler<Self::Event, Handler=RequestHandler>>) {
        self.handlers.insert(handler.handler(), handler);
    }
    fn remove_handler(&mut self, handler: &reactor::EventHandler<Self::Event, Handler=RequestHandler>){}
}

struct Demultiplexer<T>(Receiver<T>);

impl reactor::EventDemultiplexer for Demultiplexer<Request> {
    type Event = Request;
    fn select(&mut self) -> Option<Self::Event> {
        self.0.recv().ok()
    }
}

mod handlers {
    pub mod exit {
        use dispatcher::{Request, RequestHandler};
        use condition::Condition;
        use reactor::EventHandler;

        pub struct ExitHandler{
            condition: Condition,
            stops: u32
        }

        impl ExitHandler {
            pub fn new(condition: Condition) -> ExitHandler {
                ExitHandler {
                    condition: condition,
                    stops: 0
                }
            }
        }

        impl EventHandler<Request> for ExitHandler {
            type Handler = RequestHandler;
            fn handle_event(&mut self, event: Request) {
                if let Request::Exit = event {
                    self.stops += 1;

                    if self.stops >= 2 {
                        self.condition.activate();
                    }
                } else {
                    unreachable!("An ExitHandler should only receive Exit events");
                }
            }
            fn handler(&self) -> Self::Handler {
                RequestHandler::Exit
            }
        }
    }

    pub mod event {
        use dispatcher::{Request, RequestHandler};
        use reactor;

        pub struct EventHandler;

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

        impl reactor::EventHandler<Request> for EventHandler {
            type Handler = RequestHandler;
            fn handle_event(&mut self, event: Request) {
                if let Request::Event(_) = event {
                    println!("Event recvd");
                } else {
                    unreachable!("An EventHandler should only receive Event events");
                }
            }
            fn handler(&self) -> Self::Handler {
                RequestHandler::Event
            }
        }
    }

    mod linear {
        use context::Context;
        use dispatcher::{Request, RequestHandler};
        use EventHandler;
        use Event;
        use reactor;

        pub struct LinearHandler {
            contexts: Vec<Context>
        }

        impl LinearHandler {
            pub fn new() -> LinearHandler {
                LinearHandler {
                    contexts: Vec::new()
                }
            }
        }

        impl reactor::EventHandler<Event> for LinearHandler {
            type Handler = EventHandler;
            fn handle_event(&mut self, event: Event) {
                println!("timer event");
            }
            fn handler(&self) -> Self::Handler {
                EventHandler::Timer
            }
        }
    }
}
<commit_msg>dispatcher: add MessageHandler<commit_after>use std::sync::mpsc::{Receiver, Sender};
use std::rc::Rc;

use action::ExecResult;
use super::{config, Condition, Context, Message, TimerEvent};
use reactor::{self, Event, EventDemultiplexer, EventHandler, Reactor};

#[derive(Debug)]
pub enum Request {
    Event(super::Event),
    Exit
}

#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub enum RequestHandler {
    Event,
    Exit
}

impl reactor::Event for Request {
    type Handler = RequestHandler;
    fn handler(&self) -> Self::Handler {
        match *self {
            Request::Event(_) => RequestHandler::Event,
            Request::Exit => RequestHandler::Exit,
        }
    }
}

#[derive(Debug)]
pub enum Response {
    Event(ExecResult),
    Exit
}

pub struct Dispatcher {
    contexts: Vec<Context>,
    output_channel: Sender<Response>,
    exits_received: u32
}

impl Dispatcher {
    pub fn new(contexts: Vec<config::Context>, action_output_channel: Sender<Response>) -> Dispatcher {
        let contexts = contexts.into_iter().map(|ctx| Context::from(ctx)).collect::<Vec<Context>>();
        Dispatcher {
            contexts: contexts,
            output_channel: action_output_channel,
            exits_received: 0
        }
    }

    pub fn start_loop(&mut self, channel: Receiver<Request>) {
        for i in channel.iter() {
            match i {
                Request::Event(event) => self.dispatch(event),
                Request::Exit => {
                    if self.on_exit() {
                        break;
                    }
                }
            }
        }
    }

    fn on_exit(&mut self) -> bool {
        self.exits_received += 1;
        let _ = self.output_channel.send(Response::Exit);
        self.exits_received >= 2
    }

    pub fn dispatch(&mut self, event: super::Event) {
        match event {
            super::Event::Message(event) => {
                let event = Rc::new(event);
                self.on_message(event);
            },
            super::Event::Timer(ref event) => {
                self.on_timer(event);
            }
        };
    }

    fn on_message(&mut self, event: Rc<Message>) {
        for context in self.contexts.iter_mut() {
            if let Some(result) = context.on_message(event.clone()) {
                for i in result.into_iter() {
                    let _ = self.output_channel.send(i.into());
                }
            }
        }
    }

    fn on_timer(&mut self, event: &TimerEvent) {
        for context in self.contexts.iter_mut() {
            if let Some(result) = context.on_timer(event) {
                for i in result.into_iter() {
                    let _ = self.output_channel.send(i.into());
                }
            }
        }
    }
}

use std::collections::BTreeMap;

pub struct RequestReactor {
    handlers: BTreeMap<RequestHandler, Box<reactor::EventHandler<Request, Handler=RequestHandler>>>,
    demultiplexer: Demultiplexer<Request>,
    exit_condition: Condition
}

impl RequestReactor {
    fn new(demultiplexer: Demultiplexer<Request>) -> RequestReactor {
        let exit_condition = Condition::new(false);
        let exit_handler = Box::new(handlers::exit::ExitHandler::new(exit_condition.clone()));
        let event_handler = Box::new(handlers::event::EventHandler::new());

        let mut reactor = RequestReactor {
            demultiplexer: demultiplexer,
            exit_condition: exit_condition,
            handlers: BTreeMap::new()
        };

        reactor.register_handler(exit_handler);
        reactor.register_handler(event_handler);
        reactor
    }
}

impl Reactor for RequestReactor {
    type Event = Request;
    type Handler = RequestHandler;
    fn handle_events(&mut self) {
        while !self.exit_condition.is_active() {
            if let Some(request) = self.demultiplexer.select() {
                let mut handler = self.handlers.get_mut(&request.handler()).unwrap();
                handler.handle_event(request);
            } else {
                break;
            }
        }
    }
    fn register_handler(&mut self, handler: Box<reactor::EventHandler<Self::Event, Handler=RequestHandler>>) {
        self.handlers.insert(handler.handler(), handler);
    }
    fn remove_handler(&mut self, handler: &reactor::EventHandler<Self::Event, Handler=RequestHandler>){}
}

struct Demultiplexer<T>(Receiver<T>);

impl reactor::EventDemultiplexer for Demultiplexer<Request> {
    type Event = Request;
    fn select(&mut self) -> Option<Self::Event> {
        self.0.recv().ok()
    }
}

mod handlers {
    pub mod exit {
        use dispatcher::{Request, RequestHandler};
        use condition::Condition;
        use reactor::EventHandler;

        pub struct ExitHandler{
            condition: Condition,
            stops: u32
        }

        impl ExitHandler {
            pub fn new(condition: Condition) -> ExitHandler {
                ExitHandler {
                    condition: condition,
                    stops: 0
                }
            }
        }

        impl EventHandler<Request> for ExitHandler {
            type Handler = RequestHandler;
            fn handle_event(&mut self, event: Request) {
                if let Request::Exit = event {
                    self.stops += 1;

                    if self.stops >= 2 {
                        self.condition.activate();
                    }
                } else {
                    unreachable!("An ExitHandler should only receive Exit events");
                }
            }
            fn handler(&self) -> Self::Handler {
                RequestHandler::Exit
            }
        }
    }

    pub mod event {
        use dispatcher::{Request, RequestHandler};
        use reactor;

        pub struct EventHandler;

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

        impl reactor::EventHandler<Request> for EventHandler {
            type Handler = RequestHandler;
            fn handle_event(&mut self, event: Request) {
                if let Request::Event(_) = event {
                    println!("Event recvd");
                } else {
                    unreachable!("An EventHandler should only receive Event events");
                }
            }
            fn handler(&self) -> Self::Handler {
                RequestHandler::Event
            }
        }

        pub mod message {
            use std::collections::BTreeMap;

            use dispatcher::{Request, RequestHandler};
            use reactor::{self, EventHandler, Event};

            pub struct MessageHandler {
                handlers: BTreeMap<::EventHandler, Box<reactor::EventHandler<::Event, Handler=::EventHandler>>>,
            }

            impl MessageHandler {
                pub fn new() -> MessageHandler {
                    let mut handler = MessageHandler{
                        handlers: BTreeMap::new()
                    };
                    handler
                }

                fn register_handler(&mut self, handler: Box<reactor::EventHandler<::Event, Handler=::EventHandler>>) {
                    self.handlers.insert(handler.handler(), handler);
                }
            }

            impl reactor::EventHandler<::Event> for MessageHandler {
                type Handler = ::EventHandler;
                fn handle_event(&mut self, event: ::Event) {
                    println!("message event");
                }
                fn handler(&self) -> Self::Handler {
                    ::EventHandler::Message
                }
            }
        }
    }

    mod linear {
        use context::Context;
        use dispatcher::{Request, RequestHandler};
        use EventHandler;
        use Event;
        use reactor;

        pub struct LinearHandler {
            contexts: Vec<Context>
        }

        impl LinearHandler {
            pub fn new() -> LinearHandler {
                LinearHandler {
                    contexts: Vec::new()
                }
            }
        }

        impl reactor::EventHandler<Event> for LinearHandler {
            type Handler = EventHandler;
            fn handle_event(&mut self, event: Event) {
                println!("timer event");
            }
            fn handler(&self) -> Self::Handler {
                EventHandler::Timer
            }
        }
    }
}
<|endoftext|>


<commit_before>/*
    This file is a part of cargo-esr.

    Copyright (C) 2017 Mohammad AlSaleh <CE.Mohammad.AlSaleh at gmail.com>
    https://github.com/rust-alt/cargo-esr

    This Source Code Form is subject to the terms of the Mozilla Public
    License, v. 2.0. If a copy of the MPL was not distributed with this
    file, You can obtain one at <http://mozilla.org/MPL/2.0/>.
*/

use pipeliner::Pipeline;

use esr_from::{EsrFrom, DefEsrFrom};
use esr_util;
use esr_errors::*;

#[derive(Deserialize, Debug)]
struct PullRequestInfo {
    merged_at: Option<String>,
    number: usize,
}

#[derive(Deserialize, Debug)]
struct IssueInfo {
    closed_at: Option<String>,
    number: usize,
}

#[derive(Deserialize, Debug)]
struct ContributorInfo {
    contributions: usize,
}

// =================

type RepoPullRequests = Vec<PullRequestInfo>;

impl EsrFrom for RepoPullRequests {
    fn url_from_id(id: &str) -> String {
        let url = String::from("https://api.github.com/repos/:id/pulls?state=all&per_page=100");
        url.replace(":id", id)
    }
}

type RepoClosedIssues = Vec<IssueInfo>;

impl EsrFrom for RepoClosedIssues {
    fn url_from_id(id: &str) -> String {
        let url = String::from("https://api.github.com/repos/:id/issues?\
                               state=closed&per_page=1");
        url.replace(":id", id)
    }
}

type RepoContributors = Vec<ContributorInfo>;

impl EsrFrom for RepoContributors {
    fn url_from_id(id: &str) -> String {
        let url = String::from("https://api.github.com/repos/:id/contributors?per_page=100");
        url.replace(":id", id)
    }
}

#[derive(Deserialize, Debug)]
struct RepoGeneralInfo {
    subscribers_count: usize,
    created_at: String,
    // Same as created_at if the repo is empty
    pushed_at: String,
}

impl EsrFrom for RepoGeneralInfo {
    fn url_from_id(id: &str) -> String {
        let url = String::from("https://api.github.com/repos/:id");
        url.replace(":id", id)
    }
}

// =================

pub struct RepoInfo {
    general_info: RepoGeneralInfo,
    last_100_closed_issues: RepoClosedIssues,
    last_100_pull_requests: RepoPullRequests,
    top_100_contributors: RepoContributors,
}

impl RepoInfo {
    pub fn from_id(_: &str) -> Result<Self> {
        Err("Unimplemented: use  from_id_with_token()")?
    }

    pub fn from_id_with_token(id: &str, token: &str) -> Result<Self> {
        let urls = vec![
            RepoGeneralInfo::url_from_id_and_token(id, token),
            RepoClosedIssues::url_from_id_and_token(id, token),
            RepoPullRequests::url_from_id_and_token(id, token),
            RepoContributors::url_from_id_and_token(id, token),
        ];

        // `.with_threads()` does not guarantee order. So, we use `.enumerate()` as an indexer
        let bytes = urls
            .into_iter()
            .enumerate()
            .with_threads(4)
            .map(|(idx, url)| DefEsrFrom::bytes_from_url(&url).map(|bytes| (idx, bytes)))
            .collect::<Result<Vec<_>>>()?;

        let &(_, ref bytes_general) = bytes.iter().find(|&&(idx,_)| idx == 0).ok_or("impossible")?;
        let &(_, ref bytes_closed_issues) = bytes.iter().find(|&&(idx,_)| idx == 1).ok_or("impossible")?;
        let &(_, ref bytes_pulls) = bytes.iter().find(|&&(idx,_)| idx == 2).ok_or("impossible")?;
        let &(_, ref bytes_contributors) = bytes.iter().find(|&&(idx,_)| idx == 3).ok_or("impossible")?;

        Ok(Self {
            general_info: RepoGeneralInfo::from_bytes(bytes_general)?,
            last_100_closed_issues: RepoClosedIssues::from_bytes(bytes_closed_issues)?,
            last_100_pull_requests: RepoPullRequests::from_bytes(bytes_pulls)?,
            top_100_contributors: RepoContributors::from_bytes(bytes_contributors)?,
        })
    }
}

pub struct RepoScoreInfo {
    subscribers: usize,
    contributors_up_to_100: usize,
    commits_from_upto_100_contributors: usize,
    secondary_contribution_pct: usize,
    tertiary_contribution_pct: usize,
    merged_pull_requests_in_last_100: usize,
    months_since_last_pr_merged: f64,
    months_since_last_issue_closed: f64,
    push_span_in_months: f64,
    months_since_last_push: f64,
}

impl RepoScoreInfo {
    fn from_repo_info(repo_info: &RepoInfo) -> Result<Self> {
        let general_info = &repo_info.general_info;

        // Days active, months since last push
        let push_span_in_months =esr_util::span_in_months(&general_info.created_at,
                                                          &general_info.pushed_at)?;
        let months_since_last_push = esr_util::age_in_months(&general_info.pushed_at)?;

        // Get subscribers count
        let subscribers = general_info.subscribers_count;

        // Get contributor count, commit count using contributors info.
        if repo_info.top_100_contributors.is_empty() {
            Err("Empty contributors list")?;
        }

        let contributors_up_to_100 = repo_info.top_100_contributors.len();

        let commits_from_upto_100_contributors = repo_info.top_100_contributors
            .iter()
            .map(|contributor| contributor.contributions)
            .sum();

        // Secondary and Tertiary contribution pct
        let commits_f64 = commits_from_upto_100_contributors as f64;
        let top_committer_contrib = repo_info.top_100_contributors[0].contributions as f64 / commits_f64;
        let second_committer_contrib = repo_info.top_100_contributors.get(1)
            .map(|c| c.contributions as f64 / commits_f64)
            .unwrap_or(0_f64);

        let secondary_contribution_pct = ((1.0 - top_committer_contrib) * 100.0).ceil() as usize;
        let tertiary_contribution_pct = ((1.0 - top_committer_contrib - second_committer_contrib) * 100.0).ceil() as usize;

        // merged pull requests in last 100, months since last merged
        let merged_pull_requests_in_last_100 = repo_info.last_100_pull_requests
            .iter()
            .filter(|pr| pr.merged_at.is_some())
            .count();

        let last_pr_merged_opt = repo_info.last_100_pull_requests
            .iter()
            .filter(|pr| pr.merged_at.is_some())
            .nth(0);

        let months_since_last_pr_merged = match last_pr_merged_opt {
            Some(pr) => esr_util::age_in_months(pr.merged_at.as_ref().ok_or("Impossible")?)?,
            None => esr_util::age_in_months(&general_info.created_at)?,
        };

        // months since last closed
        let last_issue_closed_opt = repo_info.last_100_closed_issues.get(0);

        let months_since_last_issue_closed = match last_issue_closed_opt {
            Some(issue) => esr_util::age_in_months(issue.closed_at.as_ref().ok_or("Impossible")?)?,
            None => esr_util::age_in_months(&general_info.created_at)?,
        };

        // Done
        Ok(Self {
            subscribers,
            contributors_up_to_100,
            commits_from_upto_100_contributors,
            secondary_contribution_pct,
            tertiary_contribution_pct,
            push_span_in_months,
            merged_pull_requests_in_last_100,
            months_since_last_pr_merged,
            months_since_last_issue_closed,
            months_since_last_push,
        })
    }

    fn mk_score(&self) -> (Vec<(String, String, String)>, f64, f64) {
        let mut positive_score = 0.0;
        let mut negative_score = 0.0;
        let mut table = Vec::with_capacity(9);

        // +ve
        score_add!(table, positive_score, self.subscribers, 0.5);
        score_add!(table, positive_score, self.contributors_up_to_100, 3.0);
        score_add!(table,
                   positive_score,
                   self.commits_from_upto_100_contributors,
                   0.1);

        // We only take secondary/tertiary contribution into account if the repo has >= 50 commits
        if self.commits_from_upto_100_contributors >= 50 {
            score_add!(table, positive_score, self.secondary_contribution_pct, 2.0);
            score_add!(table, positive_score, self.tertiary_contribution_pct, 3.0);
        }

        score_add!(table,
                   positive_score,
                   self.push_span_in_months.powf(0.5),
                   5.0);
        score_add!(table,
                   positive_score,
                   self.merged_pull_requests_in_last_100,
                   2.0);

        // -ve
        score_add!(table,
                   negative_score,
                   self.months_since_last_pr_merged.powf(1.5),
                   -1.0);
        score_add!(table,
                   negative_score,
                   self.months_since_last_issue_closed.powf(1.5),
                   -1.0);
        score_add!(table,
                   negative_score,
                   self.months_since_last_push.powf(1.5),
                   -4.0);

        (table, positive_score, negative_score)
    }
}

pub struct RepoInfoWithScore {
    repo_info: RepoInfo,
    repo_score_info: RepoScoreInfo,
    score_positive: f64,
    score_negative: f64,
    score_table: Vec<(String, String, String)>,
}

impl RepoInfoWithScore {
    pub fn from_id_with_token(id: &str, token: &str) -> Result<Self> {
        let repo_info = RepoInfo::from_id_with_token(id, token)?;
        let repo_score_info = RepoScoreInfo::from_repo_info(&repo_info)?;
        let (score_table, score_positive, score_negative) = repo_score_info.mk_score();

        Ok(Self {
            repo_info,
            repo_score_info,
            score_positive,
            score_negative,
            score_table,
        })
    }

    pub fn get_info(&self) -> &RepoInfo {
        &self.repo_info
    }

    pub fn get_score_info(&self) -> &RepoScoreInfo {
        &self.repo_score_info
    }

    pub fn get_score_tuple(&self) -> (f64, f64) {
        (self.score_positive, self.score_negative)
    }

    pub fn get_score_table(&self) -> &[(String, String, String)] {
        &self.score_table
    }
}
<commit_msg>esr_github: Reduce commits weight<commit_after>/*
    This file is a part of cargo-esr.

    Copyright (C) 2017 Mohammad AlSaleh <CE.Mohammad.AlSaleh at gmail.com>
    https://github.com/rust-alt/cargo-esr

    This Source Code Form is subject to the terms of the Mozilla Public
    License, v. 2.0. If a copy of the MPL was not distributed with this
    file, You can obtain one at <http://mozilla.org/MPL/2.0/>.
*/

use pipeliner::Pipeline;

use esr_from::{EsrFrom, DefEsrFrom};
use esr_util;
use esr_errors::*;

#[derive(Deserialize, Debug)]
struct PullRequestInfo {
    merged_at: Option<String>,
    number: usize,
}

#[derive(Deserialize, Debug)]
struct IssueInfo {
    closed_at: Option<String>,
    number: usize,
}

#[derive(Deserialize, Debug)]
struct ContributorInfo {
    contributions: usize,
}

// =================

type RepoPullRequests = Vec<PullRequestInfo>;

impl EsrFrom for RepoPullRequests {
    fn url_from_id(id: &str) -> String {
        let url = String::from("https://api.github.com/repos/:id/pulls?state=all&per_page=100");
        url.replace(":id", id)
    }
}

type RepoClosedIssues = Vec<IssueInfo>;

impl EsrFrom for RepoClosedIssues {
    fn url_from_id(id: &str) -> String {
        let url = String::from("https://api.github.com/repos/:id/issues?\
                               state=closed&per_page=1");
        url.replace(":id", id)
    }
}

type RepoContributors = Vec<ContributorInfo>;

impl EsrFrom for RepoContributors {
    fn url_from_id(id: &str) -> String {
        let url = String::from("https://api.github.com/repos/:id/contributors?per_page=100");
        url.replace(":id", id)
    }
}

#[derive(Deserialize, Debug)]
struct RepoGeneralInfo {
    subscribers_count: usize,
    created_at: String,
    // Same as created_at if the repo is empty
    pushed_at: String,
}

impl EsrFrom for RepoGeneralInfo {
    fn url_from_id(id: &str) -> String {
        let url = String::from("https://api.github.com/repos/:id");
        url.replace(":id", id)
    }
}

// =================

pub struct RepoInfo {
    general_info: RepoGeneralInfo,
    last_100_closed_issues: RepoClosedIssues,
    last_100_pull_requests: RepoPullRequests,
    top_100_contributors: RepoContributors,
}

impl RepoInfo {
    pub fn from_id(_: &str) -> Result<Self> {
        Err("Unimplemented: use  from_id_with_token()")?
    }

    pub fn from_id_with_token(id: &str, token: &str) -> Result<Self> {
        let urls = vec![
            RepoGeneralInfo::url_from_id_and_token(id, token),
            RepoClosedIssues::url_from_id_and_token(id, token),
            RepoPullRequests::url_from_id_and_token(id, token),
            RepoContributors::url_from_id_and_token(id, token),
        ];

        // `.with_threads()` does not guarantee order. So, we use `.enumerate()` as an indexer
        let bytes = urls
            .into_iter()
            .enumerate()
            .with_threads(4)
            .map(|(idx, url)| DefEsrFrom::bytes_from_url(&url).map(|bytes| (idx, bytes)))
            .collect::<Result<Vec<_>>>()?;

        let &(_, ref bytes_general) = bytes.iter().find(|&&(idx,_)| idx == 0).ok_or("impossible")?;
        let &(_, ref bytes_closed_issues) = bytes.iter().find(|&&(idx,_)| idx == 1).ok_or("impossible")?;
        let &(_, ref bytes_pulls) = bytes.iter().find(|&&(idx,_)| idx == 2).ok_or("impossible")?;
        let &(_, ref bytes_contributors) = bytes.iter().find(|&&(idx,_)| idx == 3).ok_or("impossible")?;

        Ok(Self {
            general_info: RepoGeneralInfo::from_bytes(bytes_general)?,
            last_100_closed_issues: RepoClosedIssues::from_bytes(bytes_closed_issues)?,
            last_100_pull_requests: RepoPullRequests::from_bytes(bytes_pulls)?,
            top_100_contributors: RepoContributors::from_bytes(bytes_contributors)?,
        })
    }
}

pub struct RepoScoreInfo {
    subscribers: usize,
    contributors_up_to_100: usize,
    commits_from_upto_100_contributors: usize,
    secondary_contribution_pct: usize,
    tertiary_contribution_pct: usize,
    merged_pull_requests_in_last_100: usize,
    months_since_last_pr_merged: f64,
    months_since_last_issue_closed: f64,
    push_span_in_months: f64,
    months_since_last_push: f64,
}

impl RepoScoreInfo {
    fn from_repo_info(repo_info: &RepoInfo) -> Result<Self> {
        let general_info = &repo_info.general_info;

        // Days active, months since last push
        let push_span_in_months =esr_util::span_in_months(&general_info.created_at,
                                                          &general_info.pushed_at)?;
        let months_since_last_push = esr_util::age_in_months(&general_info.pushed_at)?;

        // Get subscribers count
        let subscribers = general_info.subscribers_count;

        // Get contributor count, commit count using contributors info.
        if repo_info.top_100_contributors.is_empty() {
            Err("Empty contributors list")?;
        }

        let contributors_up_to_100 = repo_info.top_100_contributors.len();

        let commits_from_upto_100_contributors = repo_info.top_100_contributors
            .iter()
            .map(|contributor| contributor.contributions)
            .sum();

        // Secondary and Tertiary contribution pct
        let commits_f64 = commits_from_upto_100_contributors as f64;
        let top_committer_contrib = repo_info.top_100_contributors[0].contributions as f64 / commits_f64;
        let second_committer_contrib = repo_info.top_100_contributors.get(1)
            .map(|c| c.contributions as f64 / commits_f64)
            .unwrap_or(0_f64);

        let secondary_contribution_pct = ((1.0 - top_committer_contrib) * 100.0).ceil() as usize;
        let tertiary_contribution_pct = ((1.0 - top_committer_contrib - second_committer_contrib) * 100.0).ceil() as usize;

        // merged pull requests in last 100, months since last merged
        let merged_pull_requests_in_last_100 = repo_info.last_100_pull_requests
            .iter()
            .filter(|pr| pr.merged_at.is_some())
            .count();

        let last_pr_merged_opt = repo_info.last_100_pull_requests
            .iter()
            .filter(|pr| pr.merged_at.is_some())
            .nth(0);

        let months_since_last_pr_merged = match last_pr_merged_opt {
            Some(pr) => esr_util::age_in_months(pr.merged_at.as_ref().ok_or("Impossible")?)?,
            None => esr_util::age_in_months(&general_info.created_at)?,
        };

        // months since last closed
        let last_issue_closed_opt = repo_info.last_100_closed_issues.get(0);

        let months_since_last_issue_closed = match last_issue_closed_opt {
            Some(issue) => esr_util::age_in_months(issue.closed_at.as_ref().ok_or("Impossible")?)?,
            None => esr_util::age_in_months(&general_info.created_at)?,
        };

        // Done
        Ok(Self {
            subscribers,
            contributors_up_to_100,
            commits_from_upto_100_contributors,
            secondary_contribution_pct,
            tertiary_contribution_pct,
            push_span_in_months,
            merged_pull_requests_in_last_100,
            months_since_last_pr_merged,
            months_since_last_issue_closed,
            months_since_last_push,
        })
    }

    fn mk_score(&self) -> (Vec<(String, String, String)>, f64, f64) {
        let mut positive_score = 0.0;
        let mut negative_score = 0.0;
        let mut table = Vec::with_capacity(9);

        // +ve
        score_add!(table, positive_score, self.subscribers, 0.5);
        score_add!(table, positive_score, self.contributors_up_to_100, 3.0);
        score_add!(table,
                   positive_score,
                   self.commits_from_upto_100_contributors,
                   0.05);

        // We only take secondary/tertiary contribution into account if the repo has >= 50 commits
        if self.commits_from_upto_100_contributors >= 50 {
            score_add!(table, positive_score, self.secondary_contribution_pct, 2.0);
            score_add!(table, positive_score, self.tertiary_contribution_pct, 3.0);
        }

        score_add!(table,
                   positive_score,
                   self.push_span_in_months.powf(0.5),
                   5.0);
        score_add!(table,
                   positive_score,
                   self.merged_pull_requests_in_last_100,
                   2.0);

        // -ve
        score_add!(table,
                   negative_score,
                   self.months_since_last_pr_merged.powf(1.5),
                   -1.0);
        score_add!(table,
                   negative_score,
                   self.months_since_last_issue_closed.powf(1.5),
                   -1.0);
        score_add!(table,
                   negative_score,
                   self.months_since_last_push.powf(1.5),
                   -4.0);

        (table, positive_score, negative_score)
    }
}

pub struct RepoInfoWithScore {
    repo_info: RepoInfo,
    repo_score_info: RepoScoreInfo,
    score_positive: f64,
    score_negative: f64,
    score_table: Vec<(String, String, String)>,
}

impl RepoInfoWithScore {
    pub fn from_id_with_token(id: &str, token: &str) -> Result<Self> {
        let repo_info = RepoInfo::from_id_with_token(id, token)?;
        let repo_score_info = RepoScoreInfo::from_repo_info(&repo_info)?;
        let (score_table, score_positive, score_negative) = repo_score_info.mk_score();

        Ok(Self {
            repo_info,
            repo_score_info,
            score_positive,
            score_negative,
            score_table,
        })
    }

    pub fn get_info(&self) -> &RepoInfo {
        &self.repo_info
    }

    pub fn get_score_info(&self) -> &RepoScoreInfo {
        &self.repo_score_info
    }

    pub fn get_score_tuple(&self) -> (f64, f64) {
        (self.score_positive, self.score_negative)
    }

    pub fn get_score_table(&self) -> &[(String, String, String)] {
        &self.score_table
    }
}
<|endoftext|>