<commit_before>use crate::utils::{
    add_where_clauses_for_new_ident, DeriveType, MultiFieldData, State,
};
use proc_macro2::{Span, TokenStream};
use quote::{quote, ToTokens};
use syn::{parse::Result, DeriveInput, Ident, Index};

/// Provides the hook to expand `#[derive(From)]` into an implementation of `From`
pub fn expand(input: &DeriveInput, trait_name: &'static str) -> Result<TokenStream> {
    let state = State::new(
        input,
        trait_name,
        quote!(::core::convert),
        trait_name.to_lowercase(),
    )?;
    if state.derive_type == DeriveType::Enum {
        Ok(enum_from(input, state))
    } else {
        Ok(struct_from(input, &state))
    }
}

pub fn struct_from(input: &DeriveInput, state: &State) -> TokenStream {
    let multi_field_data = state.enabled_fields_data();
    let MultiFieldData {
        variant_type,
        fields,
        field_idents,
        infos,
        input_type,
        trait_path,
        ..
    } = multi_field_data.clone();

    let mut new_generics = input.generics.clone();
    let sub_items: Vec<_> = infos
        .iter()
        .zip(fields.iter())
        .enumerate()
        .map(|(i, (info, field))| {
            let field_type = &field.ty;
            let variable = if fields.len() == 1 {
                quote!(original)
            } else {
                let tuple_index = Index::from(i);
                quote!(original.#tuple_index)
            };
            if info.forward {
                let type_param =
                    &Ident::new(&format!("__FromT{}", i), Span::call_site());
                let sub_trait_path = quote!(#trait_path<#type_param>);
                let type_where_clauses = quote! {
                    where #field_type: #sub_trait_path
                };
                new_generics = add_where_clauses_for_new_ident(
                    &input.generics,
                    &[field],
                    type_param,
                    type_where_clauses,
                    true,
                );
                let casted_trait = quote!(<#field_type as #sub_trait_path>);
                (quote!(#casted_trait::from(#variable)), quote!(#type_param))
            } else {
                (variable, quote!(#field_type))
            }
        })
        .collect();
    let initializers: Vec<_> = sub_items.iter().map(|i| &i.0).collect();
    let from_types: Vec<_> = sub_items.iter().map(|i| &i.1).collect();

    let body = multi_field_data.initializer(&initializers);
    let (impl_generics, _, where_clause) = new_generics.split_for_impl();
    let (_, ty_generics, _) = input.generics.split_for_impl();

    quote! {
        impl#impl_generics #trait_path<(#(#from_types),*)> for
            #input_type#ty_generics #where_clause {

            #[allow(unused_variables)]
            #[inline]
            fn from(original: (#(#from_types),*)) -> #input_type#ty_generics {
                #body
            }
        }
    }
}

fn enum_from(input: &DeriveInput, state: State) -> TokenStream {
    let mut tokens = TokenStream::new();

    for variant_state in state.enabled_variant_data().variant_states {
        struct_from(input, variant_state).to_tokens(&mut tokens);
    }
    tokens
}
<commit_msg>Fix warnings<commit_after>use crate::utils::{
    add_where_clauses_for_new_ident, DeriveType, MultiFieldData, State,
};
use proc_macro2::{Span, TokenStream};
use quote::{quote, ToTokens};
use syn::{parse::Result, DeriveInput, Ident, Index};

/// Provides the hook to expand `#[derive(From)]` into an implementation of `From`
pub fn expand(input: &DeriveInput, trait_name: &'static str) -> Result<TokenStream> {
    let state = State::new(
        input,
        trait_name,
        quote!(::core::convert),
        trait_name.to_lowercase(),
    )?;
    if state.derive_type == DeriveType::Enum {
        Ok(enum_from(input, state))
    } else {
        Ok(struct_from(input, &state))
    }
}

pub fn struct_from(input: &DeriveInput, state: &State) -> TokenStream {
    let multi_field_data = state.enabled_fields_data();
    let MultiFieldData {
        fields,
        infos,
        input_type,
        trait_path,
        ..
    } = multi_field_data.clone();

    let mut new_generics = input.generics.clone();
    let sub_items: Vec<_> = infos
        .iter()
        .zip(fields.iter())
        .enumerate()
        .map(|(i, (info, field))| {
            let field_type = &field.ty;
            let variable = if fields.len() == 1 {
                quote!(original)
            } else {
                let tuple_index = Index::from(i);
                quote!(original.#tuple_index)
            };
            if info.forward {
                let type_param =
                    &Ident::new(&format!("__FromT{}", i), Span::call_site());
                let sub_trait_path = quote!(#trait_path<#type_param>);
                let type_where_clauses = quote! {
                    where #field_type: #sub_trait_path
                };
                new_generics = add_where_clauses_for_new_ident(
                    &input.generics,
                    &[field],
                    type_param,
                    type_where_clauses,
                    true,
                );
                let casted_trait = quote!(<#field_type as #sub_trait_path>);
                (quote!(#casted_trait::from(#variable)), quote!(#type_param))
            } else {
                (variable, quote!(#field_type))
            }
        })
        .collect();
    let initializers: Vec<_> = sub_items.iter().map(|i| &i.0).collect();
    let from_types: Vec<_> = sub_items.iter().map(|i| &i.1).collect();

    let body = multi_field_data.initializer(&initializers);
    let (impl_generics, _, where_clause) = new_generics.split_for_impl();
    let (_, ty_generics, _) = input.generics.split_for_impl();

    quote! {
        impl#impl_generics #trait_path<(#(#from_types),*)> for
            #input_type#ty_generics #where_clause {

            #[allow(unused_variables)]
            #[inline]
            fn from(original: (#(#from_types),*)) -> #input_type#ty_generics {
                #body
            }
        }
    }
}

fn enum_from(input: &DeriveInput, state: State) -> TokenStream {
    let mut tokens = TokenStream::new();

    for variant_state in state.enabled_variant_data().variant_states {
        struct_from(input, variant_state).to_tokens(&mut tokens);
    }
    tokens
}
<|endoftext|>


<commit_before>use sdl::{event,render,pixels,video,keycode};
use sdl::rect::Point;
use std::default::Default;

type Renderer = render::Renderer<video::Window>;

pub struct Loop {
    renderer : Renderer,
    state : State,
}

struct FloatPoint(f64,f64);

impl Loop {
    pub fn new(renderer : Renderer) -> Loop {
        midpoint = {
            let (x,y) = renderer.get_output_size().unwrap();
            (x*32,y*32)
        };
        Loop {
            renderer : renderer,
            state : State {
                keys : Default::default(),
                player1 : PlayerShip {
                    pos : midpoint,
                    vec : FloatPoint(0.0,0.0),
                    dir : FloatPoint(0.0,1.0),
                }
            }
        }
    }

    pub fn handlekey(&mut self, keyDown : bool, key : keycode::KeyCode) {
        let mut dummy = false;
        let keys = &mut self.state.keys;
        let oldkeys = *keys;
        *match key {
            keycode::UpKey => &mut keys.up,
            keycode::DownKey => &mut keys.down,
            keycode::LeftKey => &mut keys.left,
            keycode::RightKey => &mut keys.right,
            keycode::SpaceKey => &mut keys.space,
            _ => &mut dummy // Ignore any other keys
        } = keyDown;
        if *keys != oldkeys {
            println!("{}",self.state.keys);
        }
    }

    pub fn update(&mut self) {
        let keys = self.keys;
        let player = &mut self.player1;
        if keys.up { player.accelerate() }
        if keys.down { player.decelerate() }
        if keys.left { player.turn_left() }
        if keys.right { player.turn_right() }
        self.draw();
    }

    pub fn draw(&self) {
        let renderer = &self.renderer;
        renderer.set_draw_color(pixels::RGB(0,0,0)).unwrap();
        renderer.clear().unwrap();
        renderer.set_draw_color(pixels::RGB(255,255,255)).unwrap();
        renderer.draw_point(self.player1.real_pos()).unwrap();
        renderer.present();
    }
}

struct State {
    keys : KeyState,
    player1 : PlayerShip
}

#[deriving(Default,PartialEq,Eq,Show)]
struct KeyState {
    down : bool,
    left : bool,
    right : bool,
    up: bool,
    space: bool,
}

// For smoother movement, everything is stored to 64th of a pixel accuracy
struct PlayerShip {
    pos : Point,
    vel : Point,
    angle : f32, // Zero points upwards.
    dimensions : (uint,uint),
}

impl PlayerShip {
    fn real_pos(&self) -> Point {
        Point(0,0) // TODO
    }

    fn update(&mut self) {
    }
}
<commit_msg>Refactor code somewhat, and get it to compile.<commit_after>use sdl::{event,render,pixels,video,keycode};
use sdl::rect::Point;
use std::default::Default;

type Renderer = render::Renderer<video::Window>;

pub struct Loop {
    renderer : Renderer,
    state : State,
}

impl Loop {
    pub fn new(renderer : Renderer) -> Loop {
        let output_size = renderer.get_output_size().unwrap();
        Loop {
            renderer : renderer,
            state : State::new(output_size)
        }
    }

    pub fn handlekey(&mut self, keyDown : bool, key : keycode::KeyCode) {
        let mut dummy = false;
        let keys = &mut self.state.keys;
        let oldkeys = *keys;
        *match key {
            keycode::UpKey => &mut keys.up,
            keycode::DownKey => &mut keys.down,
            keycode::LeftKey => &mut keys.left,
            keycode::RightKey => &mut keys.right,
            keycode::SpaceKey => &mut keys.space,
            _ => &mut dummy // Ignore any other keys
        } = keyDown;
        if *keys != oldkeys {
            println!("{}",*keys);
        }
    }

    pub fn update(&mut self) {
        {
            let keys = self.state.keys;
            let player = &mut self.state.player1;
            if keys.up { player.accelerate() }
            if keys.down { player.decelerate() }
            if keys.left { player.turn_left() }
            if keys.right { player.turn_right() }
        }
        self.draw();
    }

    pub fn draw(&self) {
        let renderer = &self.renderer;
        renderer.set_draw_color(pixels::RGB(0,0,0)).unwrap();
        renderer.clear().unwrap();
        renderer.set_draw_color(pixels::RGB(255,255,255)).unwrap();
        renderer.draw_point(self.state.player1.real_pos()).unwrap();
        renderer.present();
    }
}

struct State {
    keys : KeyState,
    player1 : PlayerShip
}

impl State {
    fn new(dims : (int,int)) -> State {
        let midpoint = {
        };
        State {
            keys : Default::default(),
            player1 : PlayerShip::new(dims)
        }
    }
}

#[deriving(Default,PartialEq,Eq,Show)]
struct KeyState {
    down : bool,
    left : bool,
    right : bool,
    up: bool,
    space: bool,
}

// For smoother movement, everything is stored to 64th of a pixel accuracy
struct PlayerShip {
    pos : Point,
    vel : Point,
    angle : f32, // Zero points upwards.
    dimensions : (int,int),
}

impl PlayerShip {
    fn new(dims : (int,int)) -> PlayerShip {
        PlayerShip {
            pos : Point::new(0,0),
            vel : Point::new(0,0),
            angle : 0.0,
            dimensions : dims
        }
    }

    fn real_pos(&self) -> Point {
        self.pos
    }

    fn update(&mut self) {
    }
    fn accelerate(&mut self) {
    }
    fn decelerate(&mut self) {
    }
    fn turn_left(&mut self) {
    }
    fn turn_right(&mut self) {
    }
}
<|endoftext|>


<commit_before>use NibbleVec;
use endian_type::{LittleEndian, BigEndian};

/// Trait for types which can be used to key a Radix Trie.
///
/// Types that implement this trait should be convertible to a vector of bytes
/// such that no two instances of the type convert to the same vector. This is essentially
/// serialisation, and may be combined with some serialisation library in the future.
///
/// If a type fails to implement this trait correctly, the Radix Trie will panic upon
/// encountering a conflict. Be careful!
pub trait TrieKey: PartialEq + Eq {
    /// Encode a value as a vector of bytes.
    fn encode_bytes(&self) -> Vec<u8> {
        panic!("implement this method or TrieKey::encode");
    }

    /// Encode a value as a NibbleVec.
    fn encode(&self) -> NibbleVec {
        NibbleVec::from_byte_vec(self.encode_bytes())
    }
}

/// Key comparison result.
#[derive(Debug)]
pub enum KeyMatch {
    /// The keys match up to the given index.
    Partial(usize),
    /// The first key is a prefix of the second.
    FirstPrefix,
    /// The second key is a prefix of the first.
    SecondPrefix,
    /// The keys match exactly.
    Full,
}

/// Compare two Trie keys.
pub fn match_keys(start_idx: usize, first: &NibbleVec, second: &NibbleVec) -> KeyMatch {
    let first_len = first.len() - start_idx;
    let min_length = ::std::cmp::min(first_len, second.len());

    for i in 0..min_length {
        if first.get(start_idx + i) != second.get(i) {
            return KeyMatch::Partial(i);
        }
    }

    match (first_len, second.len()) {
        (x, y) if x < y => KeyMatch::FirstPrefix,
        (x, y) if x == y => KeyMatch::Full,
        _ => KeyMatch::SecondPrefix,
    }
}

/// Check two keys for equality and panic if they differ.
pub fn check_keys<K>(key1: &K, key2: &K)
    where K: TrieKey
{
    if *key1 != *key2 {
        panic!("multiple-keys with the same bit representation.");
    }
}

/// --- TrieKey Implementations for standard types --- ///

// This blanket implementation goes into play when specialization is stabilized
// impl<T> TrieKey for T where T: Into<Vec<u8>> + Clone + Eq + PartialEq {
// fn encode_bytes(&self) -> Vec<u8> {
// self.clone().into()
// }
// }

impl TrieKey for Vec<u8> {
    fn encode_bytes(&self) -> Vec<u8> {
        self.clone()
    }
}

impl<'a> TrieKey for &'a [u8] {
    fn encode_bytes(&self) -> Vec<u8> {
        self.clone().to_vec()
    }
}

impl TrieKey for String {
    fn encode_bytes(&self) -> Vec<u8> {
        self.as_bytes().encode_bytes()
    }
}

impl<'a> TrieKey for &'a str {
    fn encode_bytes(&self) -> Vec<u8> {
        self.as_bytes().encode_bytes()
    }
}

impl TrieKey for i8 {
    fn encode_bytes(&self) -> Vec<u8> {
        let mut v: Vec<u8> = Vec::with_capacity(1);
        v.push(*self as u8);
        return v;
    }
}

impl TrieKey for u8 {
    fn encode_bytes(&self) -> Vec<u8> {
        let mut v: Vec<u8> = Vec::with_capacity(1);
        v.push(*self);
        return v;
    }
}

impl<T> TrieKey for LittleEndian<T>
    where T: Eq + Copy
{
    fn encode_bytes(&self) -> Vec<u8> {
        self.as_bytes().encode_bytes()
    }
}

impl<T> TrieKey for BigEndian<T>
    where T: Eq + Copy
{
    fn encode_bytes(&self) -> Vec<u8> {
        self.as_bytes().to_vec()
    }
}

macro_rules! int_keys {
    ( $( $t:ty ),* ) => {
        $(
        impl TrieKey for $t {
            fn encode_bytes(&self) -> Vec<u8> {
                let be: BigEndian<$t> = From::from(*self);
                be.encode_bytes()
            }
        }
        )*
    };
}

int_keys!(u16, u32, u64, i16, i32, i64, usize, isize);
<commit_msg>Document keys module.<commit_after>use NibbleVec;
use endian_type::{LittleEndian, BigEndian};

/// Trait for types which can be used to key a Radix Trie.
///
/// Types that implement this trait should be convertible to a vector of half-bytes (nibbles)
/// such that no two instances of the type convert to the same vector.
/// To protect against faulty behaviour, the trie will **panic** if it finds two distinct keys
/// of type `K` which encode to the same `NibbleVec`, so be careful!
///
/// If you would like to implement this trait for your own type, you need to implement
/// *either* `encode_bytes` or `encode`. You only need to implement one of the two.
/// If you don't implement one, your code will **panic** as soon you use the trie.
/// There is no performance penalty for implementing `encode_bytes` instead of `encode`,
/// so it is preferred except in the case where you require half-byte precision.
///
/// Many standard types implement this trait already. Integer types are encoded *big-endian*
/// by default but can be encoded little-endian using the `LittleEndian<T>` wrapper type.
pub trait TrieKey: PartialEq + Eq {
    /// Encode a value as a vector of bytes.
    fn encode_bytes(&self) -> Vec<u8> {
        panic!("implement this method or TrieKey::encode");
    }

    /// Encode a value as a NibbleVec.
    fn encode(&self) -> NibbleVec {
        NibbleVec::from_byte_vec(self.encode_bytes())
    }
}

/// Key comparison result.
#[derive(Debug)]
pub enum KeyMatch {
    /// The keys match up to the given index.
    Partial(usize),
    /// The first key is a prefix of the second.
    FirstPrefix,
    /// The second key is a prefix of the first.
    SecondPrefix,
    /// The keys match exactly.
    Full,
}

/// Compare two Trie keys.
///
/// Compares `first[start_idx .. ]` to `second`, i.e. only looks at a slice of the first key.
pub fn match_keys(start_idx: usize, first: &NibbleVec, second: &NibbleVec) -> KeyMatch {
    let first_len = first.len() - start_idx;
    let min_length = ::std::cmp::min(first_len, second.len());

    for i in 0..min_length {
        if first.get(start_idx + i) != second.get(i) {
            return KeyMatch::Partial(i);
        }
    }

    match (first_len, second.len()) {
        (x, y) if x < y => KeyMatch::FirstPrefix,
        (x, y) if x == y => KeyMatch::Full,
        _ => KeyMatch::SecondPrefix,
    }
}

/// Check two keys for equality and panic if they differ.
pub fn check_keys<K>(key1: &K, key2: &K)
    where K: TrieKey
{
    if *key1 != *key2 {
        panic!("multiple-keys with the same bit representation.");
    }
}

/// --- TrieKey Implementations for standard types --- ///

// This blanket implementation goes into play when specialization is stabilized
// impl<T> TrieKey for T where T: Into<Vec<u8>> + Clone + Eq + PartialEq {
// fn encode_bytes(&self) -> Vec<u8> {
// self.clone().into()
// }
// }

impl TrieKey for Vec<u8> {
    fn encode_bytes(&self) -> Vec<u8> {
        self.clone()
    }
}

impl<'a> TrieKey for &'a [u8] {
    fn encode_bytes(&self) -> Vec<u8> {
        self.clone().to_vec()
    }
}

impl TrieKey for String {
    fn encode_bytes(&self) -> Vec<u8> {
        self.as_bytes().encode_bytes()
    }
}

impl<'a> TrieKey for &'a str {
    fn encode_bytes(&self) -> Vec<u8> {
        self.as_bytes().encode_bytes()
    }
}

impl TrieKey for i8 {
    fn encode_bytes(&self) -> Vec<u8> {
        let mut v: Vec<u8> = Vec::with_capacity(1);
        v.push(*self as u8);
        return v;
    }
}

impl TrieKey for u8 {
    fn encode_bytes(&self) -> Vec<u8> {
        let mut v: Vec<u8> = Vec::with_capacity(1);
        v.push(*self);
        return v;
    }
}

impl<T> TrieKey for LittleEndian<T>
    where T: Eq + Copy
{
    fn encode_bytes(&self) -> Vec<u8> {
        self.as_bytes().encode_bytes()
    }
}

impl<T> TrieKey for BigEndian<T>
    where T: Eq + Copy
{
    fn encode_bytes(&self) -> Vec<u8> {
        self.as_bytes().to_vec()
    }
}

macro_rules! int_keys {
    ( $( $t:ty ),* ) => {
        $(
        impl TrieKey for $t {
            fn encode_bytes(&self) -> Vec<u8> {
                let be: BigEndian<$t> = From::from(*self);
                be.encode_bytes()
            }
        }
        )*
    };
}

int_keys!(u16, u32, u64, i16, i32, i64, usize, isize);
<|endoftext|>


<commit_before>//! Public and private key pairs.

use std::{
    collections::HashMap,
    fmt::{Debug, Formatter, Result as FmtResult},
};

use ring::signature::{Ed25519KeyPair as RingEd25519KeyPair, KeyPair as _};

use crate::{signatures::Signature, Algorithm, Error};

/// A cryptographic key pair for digitally signing data.
pub trait KeyPair: Sized {
    /// Signs a JSON object.
    ///
    /// # Parameters
    ///
    /// * message: An arbitrary series of bytes to sign.
    fn sign(&self, message: &[u8]) -> Signature;
}

/// An Ed25519 key pair.
pub struct Ed25519KeyPair {
    /// Ring's Keypair type
    keypair: RingEd25519KeyPair,

    /// The version of the key pair.
    version: String,
}

impl Ed25519KeyPair {
    /// Initializes a new key pair.
    ///
    /// # Parameters
    ///
    /// * document: PKCS8-formatted bytes containing the private & public keys.
    /// * version: The "version" of the key used for this signature.
    ///   Versions are used as an identifier to distinguish signatures generated from different keys
    ///   but using the same algorithm on the same homeserver.
    ///
    /// # Errors
    ///
    /// Returns an error if the public and private keys provided are invalid for the implementing
    /// algorithm.
    pub fn new(document: &[u8], version: String) -> Result<Self, Error> {
        let keypair = RingEd25519KeyPair::from_pkcs8(document)
            .map_err(|error| Error::new(error.to_string()))?;

        Ok(Self { keypair, version })
    }

    /// Generates a new key pair.
    ///
    /// # Returns
    ///
    /// Returns a Vec<u8> representing a pkcs8-encoded private/public keypair
    ///
    /// # Errors
    ///
    /// Returns an error if the generation failed.
    pub fn generate() -> Result<Vec<u8>, Error> {
        let document = RingEd25519KeyPair::generate_pkcs8(&ring::rand::SystemRandom::new())
            .map_err(|e| Error::new(e.to_string()))?;

        Ok(document.as_ref().to_vec())
    }
}

impl KeyPair for Ed25519KeyPair {
    fn sign(&self, message: &[u8]) -> Signature {
        Signature {
            algorithm: Algorithm::Ed25519,
            signature: self.keypair.sign(message).as_ref().to_vec(),
            version: self.version.clone(),
        }
    }
}

impl Debug for Ed25519KeyPair {
    fn fmt(&self, formatter: &mut Formatter<'_>) -> FmtResult {
        formatter
            .debug_struct("Ed25519KeyPair")
            .field("public_key", &self.keypair.public_key())
            .field("version", &self.version)
            .finish()
    }
}

/// A map from entity names to sets of public keys for that entity.
///
/// "Entity" is generally a homeserver, e.g. "example.com".
pub type PublicKeyMap = HashMap<String, PublicKeySet>;

/// A set of public keys for a single homeserver.
///
/// This is represented as a map from key ID to Base64-encoded signature.
pub type PublicKeySet = HashMap<String, String>;

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

    #[test]
    fn generate_key() {
        Ed25519KeyPair::generate().unwrap();
    }
}
<commit_msg>Add methods to get version and public key of keypairs<commit_after>//! Public and private key pairs.

use std::{
    collections::HashMap,
    fmt::{Debug, Formatter, Result as FmtResult},
};

use ring::signature::{Ed25519KeyPair as RingEd25519KeyPair, KeyPair as _};

use crate::{signatures::Signature, Algorithm, Error};

/// A cryptographic key pair for digitally signing data.
pub trait KeyPair: Sized {
    /// Signs a JSON object.
    ///
    /// # Parameters
    ///
    /// * message: An arbitrary series of bytes to sign.
    fn sign(&self, message: &[u8]) -> Signature;
}

/// An Ed25519 key pair.
pub struct Ed25519KeyPair {
    /// Ring's Keypair type
    keypair: RingEd25519KeyPair,

    /// The version of the key pair.
    version: String,
}

impl Ed25519KeyPair {
    /// Initializes a new key pair.
    ///
    /// # Parameters
    ///
    /// * document: PKCS8-formatted bytes containing the private & public keys.
    /// * version: The "version" of the key used for this signature.
    ///   Versions are used as an identifier to distinguish signatures generated from different keys
    ///   but using the same algorithm on the same homeserver.
    ///
    /// # Errors
    ///
    /// Returns an error if the public and private keys provided are invalid for the implementing
    /// algorithm.
    pub fn new(document: &[u8], version: String) -> Result<Self, Error> {
        let keypair = RingEd25519KeyPair::from_pkcs8(document)
            .map_err(|error| Error::new(error.to_string()))?;

        Ok(Self { keypair, version })
    }

    /// Generates a new key pair.
    ///
    /// # Returns
    ///
    /// Returns a Vec<u8> representing a pkcs8-encoded private/public keypair
    ///
    /// # Errors
    ///
    /// Returns an error if the generation failed.
    pub fn generate() -> Result<Vec<u8>, Error> {
        let document = RingEd25519KeyPair::generate_pkcs8(&ring::rand::SystemRandom::new())
            .map_err(|e| Error::new(e.to_string()))?;

        Ok(document.as_ref().to_vec())
    }

    /// Returns the version string for this keypair.
    pub fn version(&self) -> &str {
        &self.version
    }

    /// Returns the public key.
    pub fn public_key(&self) -> &[u8] {
        self.keypair.public_key().as_ref()
    }
}

impl KeyPair for Ed25519KeyPair {
    fn sign(&self, message: &[u8]) -> Signature {
        Signature {
            algorithm: Algorithm::Ed25519,
            signature: self.keypair.sign(message).as_ref().to_vec(),
            version: self.version.clone(),
        }
    }
}

impl Debug for Ed25519KeyPair {
    fn fmt(&self, formatter: &mut Formatter<'_>) -> FmtResult {
        formatter
            .debug_struct("Ed25519KeyPair")
            .field("public_key", &self.keypair.public_key())
            .field("version", &self.version)
            .finish()
    }
}

/// A map from entity names to sets of public keys for that entity.
///
/// "Entity" is generally a homeserver, e.g. "example.com".
pub type PublicKeyMap = HashMap<String, PublicKeySet>;

/// A set of public keys for a single homeserver.
///
/// This is represented as a map from key ID to Base64-encoded signature.
pub type PublicKeySet = HashMap<String, String>;

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

    #[test]
    fn generate_key() {
        Ed25519KeyPair::generate().unwrap();
    }
}
<|endoftext|>


<commit_before>use super::cell::*;
use super::fluid::*;
use itertools::Itertools;
use std::mem;
use rand::{Isaac64Rng, Rng};
use noise::{Brownian2, perlin2};

const SPAWN_RATE: f64 = 0.2;
const CONSUMPTION: f64 = 0.03;
const SURVIVAL_THRESHOLD: f64 = 0.05;
const INHALE_CAP: usize = 10;

#[derive(Debug, Clone)]
struct Mate {
    mate: (usize, usize),
    source: (usize, usize),
}

#[derive(Debug)]
struct Delta {
    movement_attempts: Vec<(usize, usize)>,
    mate_attempts: Vec<Mate>,
}

pub struct Hex {
    pub solution: Solution,
    pub cell: Option<Cell>,
    pub decision: Option<Decision>,
    delta: Delta,
}

impl Hex {
    pub fn color(&self) -> [f32; 4] {
        [0.25 * self.solution.fluids[1] as f32, 0.0, 0.25 * self.solution.fluids[0] as f32, 1.0]
    }
}

pub struct Grid {
    pub spawning: bool,
    pub width: usize,
    pub height: usize,
    tiles: Vec<Hex>,
}

impl Grid {
    pub fn new(width: usize, height: usize, rng: &mut Isaac64Rng) -> Self {
        Grid {
            spawning: true,
            width: width,
            height: height,
            tiles: randomizing_vec(width, height, rng),
        }
    }

    pub fn randomize(&mut self, rng: &mut Isaac64Rng) {
        self.tiles = randomizing_vec(self.width, self.height, rng);
    }

    pub fn hex(&self, x: usize, y: usize) -> &Hex {
        &self.tiles[x + y * self.width]
    }

    pub fn hex_mut(&mut self, x: usize, y: usize) -> &mut Hex {
        &mut self.tiles[x + y * self.width]
    }

    fn hex_and_neighbors(&mut self, x: usize, y: usize) -> (&mut Hex, [&Hex; 6]) {
        (unsafe { mem::transmute(self.hex_mut(x, y)) },
         if y % 2 == 0 {
            [// UpRight
             self.hex((x + self.width + 1) % self.width,
                      (y + self.height - 1) % self.height),
             // UpLeft
             self.hex(x, (y + self.height - 1) % self.height),
             // Left
             self.hex((x + self.width - 1) % self.width, y),
             // DownLeft
             self.hex(x, (y + self.height + 1) % self.height),
             // DownRight
             self.hex((x + self.width + 1) % self.width,
                      (y + self.height + 1) % self.height),
             // Right
             self.hex((x + self.width + 1) % self.width, y)]
        } else {
            [// UpRight
             self.hex(x, (y + self.height - 1) % self.height),
             // UpLeft
             self.hex((x + self.width - 1) % self.width,
                      (y + self.height - 1) % self.height),
             // Left
             self.hex((x + self.width - 1) % self.width, y),
             // DownLeft
             self.hex((x + self.width - 1) % self.width,
                      (y + self.height + 1) % self.height),
             // DownRight
             self.hex(x, (y + self.height + 1) % self.height),
             // Right
             self.hex((x + self.width + 1) % self.width, y)]
        })
    }

    pub fn cycle(&mut self, rng: &mut Isaac64Rng) {
        if self.spawning {
            self.cycle_spawn(rng);
        }
        self.cycle_cells();
        self.cycle_decisions(rng);
        self.cycle_fluids();
        self.cycle_death();
    }

    fn cycle_spawn(&mut self, rng: &mut Isaac64Rng) {
        if rng.next_f64() < SPAWN_RATE {
            self.tiles[rng.gen_range(0, self.width * self.height)].cell = Some(Cell::new(rng));
        }
    }

    fn cycle_cells(&mut self) {
        for x in 0..self.width {
            for y in 0..self.height {
                let (this, neighbors) = self.hex_and_neighbors(x, y);
                this.decision = if let Some(ref mut this_cell) = this.cell {
                    let neighbor_presents = [neighbors[0].cell.is_some(),
                                             neighbors[1].cell.is_some(),
                                             neighbors[2].cell.is_some(),
                                             neighbors[3].cell.is_some(),
                                             neighbors[4].cell.is_some(),
                                             neighbors[5].cell.is_some()];

                    Some(this_cell.decide([&this.solution.fluids,
                                           &neighbors[0].solution.fluids,
                                           &neighbors[1].solution.fluids,
                                           &neighbors[2].solution.fluids,
                                           &neighbors[3].solution.fluids,
                                           &neighbors[4].solution.fluids,
                                           &neighbors[5].solution.fluids],
                                          &neighbor_presents))
                } else {
                    None
                }
            }
        }
    }

    fn cycle_decisions(&mut self, rng: &mut Isaac64Rng) {
        // Compute the deltas resulting from the decision.
        for x in 0..self.width {
            for y in 0..self.height {
                let (width, height) = (self.width, self.height);
                let (this, neighbors) = self.hex_and_neighbors(x, y);
                // Clear the movements from the previous cycle.
                this.delta.movement_attempts.clear();
                this.delta.mate_attempts.clear();
                this.solution.coefficients = if let Some(ref decision) = this.decision {
                    decision.coefficients
                } else {
                    // Set the diffusion coefficients to the normal values.
                    [0.5, 1.0]
                };

                // Only add movements here if no cell is present.
                if this.cell.is_none() {
                    // Add any neighbor movements to the movement_attempts vector.
                    for (n, &facing) in neighbors.iter().zip(&[Direction::DownLeft,
                                                               Direction::DownRight,
                                                               Direction::Right,
                                                               Direction::UpRight,
                                                               Direction::UpLeft,
                                                               Direction::Left]) {
                        match n.decision {
                            Some(Decision { choice: Choice::Move(direction), .. }) => {
                                // It attempted to move into this hex cell.
                                if facing == direction {
                                    this.delta
                                        .movement_attempts
                                        .push(in_direction(x, y, width, height, facing.flip()));

                                    // No need to continue if we reach 2 attempts.
                                    if this.delta.movement_attempts.len() == 2 {
                                        break;
                                    }
                                }
                            }
                            Some(Decision { choice: Choice::Divide { mate, spawn }, .. }) => {
                                // It attempted to spawn into this hex cell.
                                if facing == spawn {
                                    let source = in_direction(x, y, width, height, facing.flip());;
                                    this.delta
                                        .mate_attempts
                                        .push(Mate {
                                            mate: in_direction(source.0,
                                                               source.1,
                                                               width,
                                                               height,
                                                               mate),
                                            source: source,
                                        });

                                    // No need to continue if we reach 2 attempts.
                                    if this.delta.mate_attempts.len() == 2 {
                                        break;
                                    }
                                }
                            }
                            _ => {}
                        }
                    }
                }
            }
        }

        // Perform the deltas.
        for x in 0..self.width {
            for y in 0..self.height {
                // Handle movement.
                if self.hex(x, y).delta.movement_attempts.len() == 1 {
                    let from_coord = self.hex(x, y).delta.movement_attempts[0];
                    self.hex_mut(x, y).cell = self.hex_mut(from_coord.0, from_coord.1).cell.take();
                    // Handle mating.
                } else if self.hex(x, y).delta.mate_attempts.len() == 1 {
                    let mate = self.hex(x, y).delta.mate_attempts[0].clone();
                    self.hex_mut(x, y).cell = if mate.mate == (x, y) {
                        Some(self.hex(mate.source.0, mate.source.1)
                            .cell
                            .as_ref()
                            .unwrap()
                            .divide(rng))
                    } else {
                        if self.hex(mate.mate.0, mate.mate.1).cell.is_some() {
                            Some(self.hex(mate.source.0, mate.source.1)
                                .cell
                                .as_ref()
                                .unwrap()
                                .mate(&self.hex(mate.mate.0, mate.mate.1)
                                          .cell
                                          .as_ref()
                                          .unwrap(),
                                      rng))
                        } else {
                            None
                        }
                    };
                }

                // Clear the decisions.
                self.hex_mut(x, y).decision = None;
            }
        }
    }

    fn cycle_fluids(&mut self) {
        // Then update diffusion.
        for x in 0..self.width {
            for y in 0..self.height {
                let (this, neighbors) = self.hex_and_neighbors(x, y);

                for n in &neighbors {
                    this.solution.diffuse_from(&n.solution);
                }
            }
        }

        // Finish the cycle.
        for hex in &mut self.tiles {
            hex.solution.end_cycle();
        }
    }

    fn cycle_death(&mut self) {
        // Finish the cycle.
        for hex in &mut self.tiles {
            if hex.cell.is_some() {
                if hex.solution.fluids[0] <= CONSUMPTION {
                    if hex.cell.as_ref().unwrap().inhale != 0 {
                        hex.cell.as_mut().unwrap().inhale -= 1;
                    } else {
                        hex.cell = None;
                    }
                } else {
                    hex.solution.fluids[0] -= CONSUMPTION;
                    if hex.solution.fluids[0] < SURVIVAL_THRESHOLD {
                        if hex.cell.as_ref().unwrap().inhale != 0 {
                            hex.cell.as_mut().unwrap().inhale -= 1;
                        } else {
                            hex.cell = None;
                        }
                    } else {
                        if hex.cell.as_ref().unwrap().inhale < INHALE_CAP {
                            hex.cell.as_mut().unwrap().inhale += 1;
                        }
                    }
                }
            }
        }
    }
}

fn randomizing_vec(width: usize, height: usize, rng: &mut Isaac64Rng) -> Vec<Hex> {
    let seeds = [rng.gen(), rng.gen()];
    let noise = Brownian2::new(perlin2, 4).wavelength(32.0);
    (0..height)
        .cartesian_product((0..width))
        .map(|(x, y)| {
            Hex {
                solution: Solution::new([noise.apply(&seeds[0], &[x as f64, y as f64]), 1.0],
                                        [0.5, 1.0]),
                cell: None,
                decision: None,
                delta: Delta {
                    movement_attempts: Vec::with_capacity(6),
                    mate_attempts: Vec::with_capacity(6),
                },
            }
        })
        .collect_vec()
}

fn in_direction(x: usize,
                y: usize,
                width: usize,
                height: usize,
                direction: Direction)
                -> (usize, usize) {
    let diff = direction.delta(y % 2 == 0);
    (((width + x) as isize + diff.0) as usize % width,
     ((height + y) as isize + diff.1) as usize % height)
}
<commit_msg>make it less strict<commit_after>use super::cell::*;
use super::fluid::*;
use itertools::Itertools;
use std::mem;
use rand::{Isaac64Rng, Rng};
use noise::{Brownian2, perlin2};

const SPAWN_RATE: f64 = 0.1;
const CONSUMPTION: f64 = 0.002;
const SURVIVAL_THRESHOLD: f64 = 0.1;
const INHALE_CAP: usize = 20;

#[derive(Debug, Clone)]
struct Mate {
    mate: (usize, usize),
    source: (usize, usize),
}

#[derive(Debug)]
struct Delta {
    movement_attempts: Vec<(usize, usize)>,
    mate_attempts: Vec<Mate>,
}

pub struct Hex {
    pub solution: Solution,
    pub cell: Option<Cell>,
    pub decision: Option<Decision>,
    delta: Delta,
}

impl Hex {
    pub fn color(&self) -> [f32; 4] {
        [0.25 * self.solution.fluids[1] as f32, 0.0, 0.25 * self.solution.fluids[0] as f32, 1.0]
    }
}

pub struct Grid {
    pub spawning: bool,
    pub width: usize,
    pub height: usize,
    tiles: Vec<Hex>,
}

impl Grid {
    pub fn new(width: usize, height: usize, rng: &mut Isaac64Rng) -> Self {
        Grid {
            spawning: true,
            width: width,
            height: height,
            tiles: randomizing_vec(width, height, rng),
        }
    }

    pub fn randomize(&mut self, rng: &mut Isaac64Rng) {
        self.tiles = randomizing_vec(self.width, self.height, rng);
    }

    pub fn hex(&self, x: usize, y: usize) -> &Hex {
        &self.tiles[x + y * self.width]
    }

    pub fn hex_mut(&mut self, x: usize, y: usize) -> &mut Hex {
        &mut self.tiles[x + y * self.width]
    }

    fn hex_and_neighbors(&mut self, x: usize, y: usize) -> (&mut Hex, [&Hex; 6]) {
        (unsafe { mem::transmute(self.hex_mut(x, y)) },
         if y % 2 == 0 {
            [// UpRight
             self.hex((x + self.width + 1) % self.width,
                      (y + self.height - 1) % self.height),
             // UpLeft
             self.hex(x, (y + self.height - 1) % self.height),
             // Left
             self.hex((x + self.width - 1) % self.width, y),
             // DownLeft
             self.hex(x, (y + self.height + 1) % self.height),
             // DownRight
             self.hex((x + self.width + 1) % self.width,
                      (y + self.height + 1) % self.height),
             // Right
             self.hex((x + self.width + 1) % self.width, y)]
        } else {
            [// UpRight
             self.hex(x, (y + self.height - 1) % self.height),
             // UpLeft
             self.hex((x + self.width - 1) % self.width,
                      (y + self.height - 1) % self.height),
             // Left
             self.hex((x + self.width - 1) % self.width, y),
             // DownLeft
             self.hex((x + self.width - 1) % self.width,
                      (y + self.height + 1) % self.height),
             // DownRight
             self.hex(x, (y + self.height + 1) % self.height),
             // Right
             self.hex((x + self.width + 1) % self.width, y)]
        })
    }

    pub fn cycle(&mut self, rng: &mut Isaac64Rng) {
        if self.spawning {
            self.cycle_spawn(rng);
        }
        self.cycle_cells();
        self.cycle_decisions(rng);
        self.cycle_fluids();
        self.cycle_death();
    }

    fn cycle_spawn(&mut self, rng: &mut Isaac64Rng) {
        if rng.next_f64() < SPAWN_RATE {
            self.tiles[rng.gen_range(0, self.width * self.height)].cell = Some(Cell::new(rng));
        }
    }

    fn cycle_cells(&mut self) {
        for x in 0..self.width {
            for y in 0..self.height {
                let (this, neighbors) = self.hex_and_neighbors(x, y);
                this.decision = if let Some(ref mut this_cell) = this.cell {
                    let neighbor_presents = [neighbors[0].cell.is_some(),
                                             neighbors[1].cell.is_some(),
                                             neighbors[2].cell.is_some(),
                                             neighbors[3].cell.is_some(),
                                             neighbors[4].cell.is_some(),
                                             neighbors[5].cell.is_some()];

                    Some(this_cell.decide([&this.solution.fluids,
                                           &neighbors[0].solution.fluids,
                                           &neighbors[1].solution.fluids,
                                           &neighbors[2].solution.fluids,
                                           &neighbors[3].solution.fluids,
                                           &neighbors[4].solution.fluids,
                                           &neighbors[5].solution.fluids],
                                          &neighbor_presents))
                } else {
                    None
                }
            }
        }
    }

    fn cycle_decisions(&mut self, rng: &mut Isaac64Rng) {
        // Compute the deltas resulting from the decision.
        for x in 0..self.width {
            for y in 0..self.height {
                let (width, height) = (self.width, self.height);
                let (this, neighbors) = self.hex_and_neighbors(x, y);
                // Clear the movements from the previous cycle.
                this.delta.movement_attempts.clear();
                this.delta.mate_attempts.clear();
                this.solution.coefficients = if let Some(ref decision) = this.decision {
                    decision.coefficients
                } else {
                    // Set the diffusion coefficients to the normal values.
                    [0.5, 1.0]
                };

                // Only add movements here if no cell is present.
                if this.cell.is_none() {
                    // Add any neighbor movements to the movement_attempts vector.
                    for (n, &facing) in neighbors.iter().zip(&[Direction::DownLeft,
                                                               Direction::DownRight,
                                                               Direction::Right,
                                                               Direction::UpRight,
                                                               Direction::UpLeft,
                                                               Direction::Left]) {
                        match n.decision {
                            Some(Decision { choice: Choice::Move(direction), .. }) => {
                                // It attempted to move into this hex cell.
                                if facing == direction {
                                    this.delta
                                        .movement_attempts
                                        .push(in_direction(x, y, width, height, facing.flip()));

                                    // No need to continue if we reach 2 attempts.
                                    if this.delta.movement_attempts.len() == 2 {
                                        break;
                                    }
                                }
                            }
                            Some(Decision { choice: Choice::Divide { mate, spawn }, .. }) => {
                                // It attempted to spawn into this hex cell.
                                if facing == spawn {
                                    let source = in_direction(x, y, width, height, facing.flip());;
                                    this.delta
                                        .mate_attempts
                                        .push(Mate {
                                            mate: in_direction(source.0,
                                                               source.1,
                                                               width,
                                                               height,
                                                               mate),
                                            source: source,
                                        });

                                    // No need to continue if we reach 2 attempts.
                                    if this.delta.mate_attempts.len() == 2 {
                                        break;
                                    }
                                }
                            }
                            _ => {}
                        }
                    }
                }
            }
        }

        // Perform the deltas.
        for x in 0..self.width {
            for y in 0..self.height {
                // Handle movement.
                if self.hex(x, y).delta.movement_attempts.len() == 1 {
                    let from_coord = self.hex(x, y).delta.movement_attempts[0];
                    self.hex_mut(x, y).cell = self.hex_mut(from_coord.0, from_coord.1).cell.take();
                    // Handle mating.
                } else if self.hex(x, y).delta.mate_attempts.len() == 1 {
                    let mate = self.hex(x, y).delta.mate_attempts[0].clone();
                    self.hex_mut(x, y).cell = if mate.mate == (x, y) {
                        Some(self.hex(mate.source.0, mate.source.1)
                            .cell
                            .as_ref()
                            .unwrap()
                            .divide(rng))
                    } else {
                        if self.hex(mate.mate.0, mate.mate.1).cell.is_some() {
                            Some(self.hex(mate.source.0, mate.source.1)
                                .cell
                                .as_ref()
                                .unwrap()
                                .mate(&self.hex(mate.mate.0, mate.mate.1)
                                          .cell
                                          .as_ref()
                                          .unwrap(),
                                      rng))
                        } else {
                            None
                        }
                    };
                }

                // Clear the decisions.
                self.hex_mut(x, y).decision = None;
            }
        }
    }

    fn cycle_fluids(&mut self) {
        // Then update diffusion.
        for x in 0..self.width {
            for y in 0..self.height {
                let (this, neighbors) = self.hex_and_neighbors(x, y);

                for n in &neighbors {
                    this.solution.diffuse_from(&n.solution);
                }
            }
        }

        // Finish the cycle.
        for hex in &mut self.tiles {
            hex.solution.end_cycle();
        }
    }

    fn cycle_death(&mut self) {
        // Finish the cycle.
        for hex in &mut self.tiles {
            if hex.cell.is_some() {
                if hex.solution.fluids[0] <= CONSUMPTION {
                    if hex.cell.as_ref().unwrap().inhale != 0 {
                        hex.cell.as_mut().unwrap().inhale -= 1;
                    } else {
                        hex.cell = None;
                    }
                } else {
                    hex.solution.fluids[0] -= CONSUMPTION;
                    if hex.solution.fluids[0] < SURVIVAL_THRESHOLD {
                        if hex.cell.as_ref().unwrap().inhale != 0 {
                            hex.cell.as_mut().unwrap().inhale -= 1;
                        } else {
                            hex.cell = None;
                        }
                    } else {
                        if hex.cell.as_ref().unwrap().inhale < INHALE_CAP {
                            hex.cell.as_mut().unwrap().inhale += 1;
                        }
                    }
                }
            }
        }
    }
}

fn randomizing_vec(width: usize, height: usize, rng: &mut Isaac64Rng) -> Vec<Hex> {
    let seeds = [rng.gen(), rng.gen()];
    let noise = Brownian2::new(perlin2, 4).wavelength(32.0);
    (0..height)
        .cartesian_product((0..width))
        .map(|(x, y)| {
            Hex {
                solution: Solution::new([noise.apply(&seeds[0], &[x as f64, y as f64]), 1.0],
                                        [0.5, 1.0]),
                cell: None,
                decision: None,
                delta: Delta {
                    movement_attempts: Vec::with_capacity(6),
                    mate_attempts: Vec::with_capacity(6),
                },
            }
        })
        .collect_vec()
}

fn in_direction(x: usize,
                y: usize,
                width: usize,
                height: usize,
                direction: Direction)
                -> (usize, usize) {
    let diff = direction.delta(y % 2 == 0);
    (((width + x) as isize + diff.0) as usize % width,
     ((height + y) as isize + diff.1) as usize % height)
}
<|endoftext|>


<commit_before>// Worklog is for recording your hours.
//

extern crate chrono;
extern crate csv;
extern crate clap;
extern crate rustc_serialize;

mod error;
mod timeclock;
mod util;

use chrono::*;
use clap::{Arg, ArgGroup, App};
use error::WorklogError;
use std::env;
use std::fs::OpenOptions;
use std::io::prelude::*;
use std::path::PathBuf;
use timeclock::Direction;
use timeclock::now;

#[cfg(target_family = "unix")]
static CSV_FILE_NAME: &'static str = ".worklog.csv";
#[cfg(target_family = "windows")]
static CSV_FILE_NAME: &'static str = "worklog.csv";

// TODO This belongs in a config file somewhere
const WEEKSTART: i64 = Weekday::Sat as i64;

fn print_csv_entries<R: Read>(file: R) -> Result<(), WorklogError> {
    let csv_entries = try!(timeclock::read_timesheet(file));
    for rec in csv_entries {
        println!("{}", rec);
    }
    Ok(())
}

fn print_full_summary<R: Read>(file: R,
                               rounding: util::Rounding)
                               -> Result<(), WorklogError> {
    let csv_entries = try!(timeclock::read_timesheet(file));
    let records = timeclock::collect_date_records(csv_entries);

    let mut total_hours: f64 = 0.0;
    for rec in records {
        let hours = util::round(rec.seconds(), rounding) / 3600.0;
        total_hours += hours;
        println!("{} {:.2} {}", rec.date().format("%F"), hours, rec.memo());
    }

    println!("Total Hours: {:.2}", total_hours);
    Ok(())
}

fn print_short_summary<R: Read>(file: R,
                                start_date: Date<FixedOffset>,
                                end_date: Date<FixedOffset>,
                                rounding: util::Rounding)
                                -> Result<(), WorklogError> {
    let csv_entries = try!(timeclock::read_timesheet(file));
    let records = timeclock::collect_date_records(csv_entries);

    let mut total_hours: f64 = 0.0;
    for rec in records {
        if start_date <= rec.date() && rec.date() <= end_date {
            let hours = util::round(rec.seconds(), rounding) / 3600.0;
            total_hours += hours;
            println!("{} {:.2} {}", rec.date().format("%F"), hours, rec.memo());
        }
    }

    println!("Total Hours: {:.2}", total_hours);
    Ok(())
}

/// Get the path for the csv data file
fn get_csv_path() -> Result<PathBuf, WorklogError> {
    let mut data_path = match env::home_dir() {
        Some(path) => path,
        None => {
            return Err(env::VarError::NotPresent).map_err(WorklogError::Env)
        }
    };

    data_path.push(CSV_FILE_NAME);
    Ok(data_path)
}

fn main0() -> Result<(), WorklogError> {
    // Using std env macro rather than depending on clap's. No difference
    // as far as I can tell.
    let matches = App::new(env!("CARGO_PKG_NAME"))
        .version(env!("CARGO_PKG_VERSION"))
        .author(env!("CARGO_PKG_AUTHORS"))
        .about(env!("CARGO_PKG_DESCRIPTION"))
        .arg(Arg::from_usage("[in] -i, --in 'Record an In entry'"))
        .arg(Arg::from_usage("[out] -o, --out 'Record an Out entry'"))
        .arg(Arg::from_usage("[time] -t, --time <TIME> 'time'")
            .requires("inout"))
        .arg(Arg::from_usage("[memo] -m, --memo <MEMO> 'Memo for the entry'")
            .requires("inout"))
        .arg(Arg::from_usage("[summary] -s, --summary 'Print a summary'")
            .conflicts_with("inout"))
        .arg(Arg::from_usage("[log] -l, --log 'Print the full log'")
            .conflicts_with("inout"))
        .arg(Arg::from_usage("[round] -r, --round 'Round totals up to the next quarter hour'")
            .conflicts_with("inout"))
        .arg(Arg::from_usage("[range] -R, --range <TIME> <TIME> 'range'"))
        .group(ArgGroup::with_name("inout").args(&["in", "out"]))
        .get_matches();

    let csv_path = try!(get_csv_path());

    let mut csv_file = try!(OpenOptions::new()
        .read(true)
        .write(true)
        .create(true)
        .open(csv_path));

    let rounding = {
        if matches.is_present("round") {
            // round up in 15min intervals
            util::Rounding::Up(900.0)
        } else {
            util::Rounding::None
        }
    };

    if matches.is_present("in") || matches.is_present("out") {
        let dir = if matches.is_present("in") {
            Direction::In
        } else {
            Direction::Out
        };

        let time = matches.value_of("time").unwrap_or("now");
        let time = try!(util::parse_multi_time_fmt(&time));

        let memo = matches.value_of("memo").unwrap_or("").to_owned();
        timeclock::mark_time(dir, time, memo, &mut csv_file);

        println!("Clocked {:#} at {}", dir, time.format("%F %I:%M %P"));

    } else if matches.is_present("summary") {
        try!(print_full_summary(&csv_file, rounding));

    } else if matches.is_present("log") {
        try!(print_csv_entries(&csv_file));

    } else if matches.is_present("range") {
        let range = matches.values_of("range").unwrap();//.collect();
        let range: Vec<DateTime<FixedOffset>> =
            try!(range.map(util::parse_multi_time_fmt).collect());
        try!(print_short_summary(&csv_file,
                                 range[0].date(),
                                 range[1].date(),
                                 rounding));

    } else {
        let today = now().date();
        let weekday = today.weekday() as i64;
        let days_back = (7 - WEEKSTART + weekday) % 7;
        let start_date = today - Duration::days(days_back);
        try!(print_short_summary(&csv_file, start_date, today, rounding));
    }

    Ok(())
}

fn main() {
    match main0() {
        Ok(_) => {}
        Err(err) => {
            let _ = writeln!(&mut std::io::stderr(), "Error: {}", err);
        }
    };
}
<commit_msg>Added conflicts_with to options<commit_after>// Worklog is for recording your hours.
//

extern crate chrono;
extern crate csv;
extern crate clap;
extern crate rustc_serialize;

mod error;
mod timeclock;
mod util;

use chrono::*;
use clap::{Arg, ArgGroup, App};
use error::WorklogError;
use std::env;
use std::fs::OpenOptions;
use std::io::prelude::*;
use std::path::PathBuf;
use timeclock::Direction;
use timeclock::now;

#[cfg(target_family = "unix")]
static CSV_FILE_NAME: &'static str = ".worklog.csv";
#[cfg(target_family = "windows")]
static CSV_FILE_NAME: &'static str = "worklog.csv";

// TODO This belongs in a config file somewhere
const WEEKSTART: i64 = Weekday::Sat as i64;

fn print_csv_entries<R: Read>(file: R) -> Result<(), WorklogError> {
    let csv_entries = try!(timeclock::read_timesheet(file));
    for rec in csv_entries {
        println!("{}", rec);
    }
    Ok(())
}

fn print_full_summary<R: Read>(file: R,
                               rounding: util::Rounding)
                               -> Result<(), WorklogError> {
    let csv_entries = try!(timeclock::read_timesheet(file));
    let records = timeclock::collect_date_records(csv_entries);

    let mut total_hours: f64 = 0.0;
    for rec in records {
        let hours = util::round(rec.seconds(), rounding) / 3600.0;
        total_hours += hours;
        println!("{} {:.2} {}", rec.date().format("%F"), hours, rec.memo());
    }

    println!("Total Hours: {:.2}", total_hours);
    Ok(())
}

fn print_short_summary<R: Read>(file: R,
                                start_date: Date<FixedOffset>,
                                end_date: Date<FixedOffset>,
                                rounding: util::Rounding)
                                -> Result<(), WorklogError> {
    let csv_entries = try!(timeclock::read_timesheet(file));
    let records = timeclock::collect_date_records(csv_entries);

    let mut total_hours: f64 = 0.0;
    for rec in records {
        if start_date <= rec.date() && rec.date() <= end_date {
            let hours = util::round(rec.seconds(), rounding) / 3600.0;
            total_hours += hours;
            println!("{} {:.2} {}", rec.date().format("%F"), hours, rec.memo());
        }
    }

    println!("Total Hours: {:.2}", total_hours);
    Ok(())
}

/// Get the path for the csv data file
fn get_csv_path() -> Result<PathBuf, WorklogError> {
    let mut data_path = match env::home_dir() {
        Some(path) => path,
        None => {
            return Err(env::VarError::NotPresent).map_err(WorklogError::Env)
        }
    };

    data_path.push(CSV_FILE_NAME);
    Ok(data_path)
}

fn main0() -> Result<(), WorklogError> {
    // Using std env macro rather than depending on clap's. No difference
    // as far as I can tell.
    let matches = App::new(env!("CARGO_PKG_NAME"))
        .version(env!("CARGO_PKG_VERSION"))
        .author(env!("CARGO_PKG_AUTHORS"))
        .about(env!("CARGO_PKG_DESCRIPTION"))
        .arg(Arg::from_usage("[in] -i, --in 'Record an In entry'"))
        .arg(Arg::from_usage("[out] -o, --out 'Record an Out entry'"))
        .arg(Arg::from_usage("[time] -t, --time <TIME> 'time'")
            .requires("inout"))
        .arg(Arg::from_usage("[memo] -m, --memo <MEMO> 'Memo for the entry'")
            .requires("inout"))
        .arg(Arg::from_usage("[summary] -s, --summary 'Print a summary'")
            .conflicts_with("inout"))
        .arg(Arg::from_usage("[log] -l, --log 'Print the full log'")
            .conflicts_with("summary")
            .conflicts_with("inout"))
        .arg(Arg::from_usage("[round] -r, --round 'Round totals up to the next quarter hour'")
            .conflicts_with("log")
            .conflicts_with("inout"))
        .arg(Arg::from_usage("[range] -R, --range <TIME> <TIME> 'range'")
            .conflicts_with("summary")
            .conflicts_with("log")
            .conflicts_with("inout"))
        .group(ArgGroup::with_name("inout").args(&["in", "out"]))
        .get_matches();

    let csv_path = try!(get_csv_path());

    let mut csv_file = try!(OpenOptions::new()
        .read(true)
        .write(true)
        .create(true)
        .open(csv_path));

    let rounding = {
        if matches.is_present("round") {
            // round up in 15min intervals
            util::Rounding::Up(900.0)
        } else {
            util::Rounding::None
        }
    };

    if matches.is_present("in") || matches.is_present("out") {
        let dir = if matches.is_present("in") {
            Direction::In
        } else {
            Direction::Out
        };

        let time = matches.value_of("time").unwrap_or("now");
        let time = try!(util::parse_multi_time_fmt(&time));

        let memo = matches.value_of("memo").unwrap_or("").to_owned();
        timeclock::mark_time(dir, time, memo, &mut csv_file);

        println!("Clocked {:#} at {}", dir, time.format("%F %I:%M %P"));

    } else if matches.is_present("summary") {
        try!(print_full_summary(&csv_file, rounding));

    } else if matches.is_present("log") {
        try!(print_csv_entries(&csv_file));

    } else if matches.is_present("range") {
        let range = matches.values_of("range").unwrap();//.collect();
        let range: Vec<DateTime<FixedOffset>> =
            try!(range.map(util::parse_multi_time_fmt).collect());
        try!(print_short_summary(&csv_file,
                                 range[0].date(),
                                 range[1].date(),
                                 rounding));

    } else {
        let today = now().date();
        let weekday = today.weekday() as i64;
        let days_back = (7 - WEEKSTART + weekday) % 7;
        let start_date = today - Duration::days(days_back);
        try!(print_short_summary(&csv_file, start_date, today, rounding));
    }

    Ok(())
}

fn main() {
    match main0() {
        Ok(_) => {}
        Err(err) => {
            let _ = writeln!(&mut std::io::stderr(), "Error: {}", err);
        }
    };
}
<|endoftext|>


<commit_before>#![feature(os)]
#![feature(core)]
#![feature(collections)]
#![feature(unicode)]
#![feature(str_char)]

extern crate git2;
extern crate chrono;
extern crate getopts;
extern crate unicode;

use getopts::Options;
use std::os;
use std::path::Path;

#[cfg(not(test))]
fn main() {
    let args: Vec<String> = os::args();
    let program = args[0].clone();

    let mut opts = Options::new();
    opts.optflag("h", "help", "print this help menu");

    let matches = match opts.parse(args.tail()) {
        Ok(m) => { m }
        Err(f) => { panic!(f.to_string()) }
    };

    if matches.opt_present("h") {
        print_usage(program.as_slice(), opts);
        return;
    }

    let input = if !matches.free.is_empty() {
        println!("count of opts: {}", matches.free.len());
        matches.free[0].clone()
    } else {
        print_usage(program.as_slice(), opts);
        return;
    };

    let path = Path::new(input.as_slice());

    let stat = gitstat::run(&path).ok().expect("All gonna bad");
    println!("Total count of commits in '{}' is: {}", path.display(), stat.total);
}

fn print_usage(program: &str, opts: Options) {
    let brief = format!("Usage: {} [options]", program);
    print!("{}", opts.usage(brief.as_slice()));
}

pub struct Stat {
    total: usize,
}

mod gitstat {
    use git2::{Repository,Commit,Oid,Signature};
    use std::collections::HashMap;
    use std::collections::hash_map::Entry;
    use std::sync::{Mutex, Arc};
    use std::path::Path;
    use chrono::datetime::DateTime;
    use chrono::naive::datetime::NaiveDateTime;
    use chrono::{Timelike, Datelike};
    use std::cmp;
    use Stat;

    pub fn run(path: &Path) -> Result<Stat, &str> {
        let repo = Box::new(Repository::open(path).ok().expect("Not valid git repository"));

        let commits: Box<Vec<Oid>> = self::depth_first_search(&repo);
        let authors: Box<HashMap<String, usize>> = self::get_authors(path, &commits);

        // iterate over everything.
        for (name, count) in authors.iter() {
            println!("{}: {}", *name, *count);
        }

        let total = commits.len();

        Ok(Stat { total: total })
    }

    /// Helper method for gets HEAD commit of given git repository
    fn get_head_commit(repo: &Box<Repository>) -> Option<Commit> {
        repo.head().ok()
            .and_then(|h| h.target())
            .and_then(|oid| repo.find_commit(oid).ok())
    }

    fn depth_first_search(repo: &Box<Repository>) -> Box<Vec<Oid>> {
        let mut visited: Box<Vec<Oid>> = Box::new(Vec::new());
        let head_commit = match self::get_head_commit(repo) {
            Some(commit) => commit,
            None => panic!("It seems like the repository is corrupt"),
        };

        let mut stack: Vec<Commit> = vec![head_commit];

        loop {
            let (id, commit) = match stack.pop() {
                Some(commit) => (commit.id(), commit),
                None => break,
            };

            if !visited.contains(&id) {
                visited.push(id);
                for parent in commit.parents() {
                    stack.push(parent);
                }
            }
        }

        visited
    }

    fn get_authors(path: &Path, oids: &Box<Vec<Oid>>) -> Box<HashMap<String, usize>> {
        let mut authors: Box<HashMap<String, usize>> = Box::new(HashMap::new());
        let mut days = [[0u32; 24]; 7];
        let repo = Repository::open(path).ok().expect("Not valid git repository");
        // let mutex = Mutex::new(repo);

        // mailmap::read_mailmap_file(path);
        for oid in oids.iter() {
            let commit = repo.find_commit(*oid).ok().expect("Commit not exists in repository");
            let author: Signature = commit.author();
            let uniq_name = format!("{} <{}>", author.name().unwrap(), author.email().unwrap());

            let time = commit.time();
            let timestamp: NaiveDateTime = NaiveDateTime::from_timestamp(time.seconds(), 0);
            let weekday: u32 = timestamp.weekday().num_days_from_monday();
            let hour: u32 = timestamp.hour();

            days[weekday as usize][hour as usize] += 1;
            // println!("{} {}", time.seconds(), time.offset_minutes());

            match authors.entry(uniq_name) {
                Entry::Vacant(entry) => entry.insert(1),
                Entry::Occupied(mut entry) => {
                    *entry.get_mut() += 1;
                    entry.into_mut()
                }
            };
        }

        // find max
        let mut max: u32 = 0;
        for i in 0..6 {
            for j in 0..23 {
                max = cmp::max(days[i as usize][j as usize], max);
            }
        }

        // let arts = ['▫', '▪', '◽', '◾', '◻', '◼', '□', '■', '⬛'];
        let arts = ['▪', '◾', '◼', '⬛'];

        for hours in days.iter() {
            for count in hours.iter() {
                print!("{:4}", arts[(count % arts.len() as u32) as usize]);
            }
            println!("");
        }

        authors
    }

    /// Module `mailmap` which implement logic for parse .mailmap files
    mod mailmap {

        use std::env;
        use std::fs::File;
        use std::io::{BufReader, BufRead};
        use std::string::String;
        use std::path::Path;
        use unicode::str::UnicodeStr;

        pub fn read_mailmap_file(basedir: &Path) {
            // get full path to .mailmap
            let path = match env::current_dir() {
                Ok(path) => path.join(".mailmap"),
                Err(error) => panic!("{}", error),
            };

            // create BufReader instance for .mailmap file
            let mut reader = match File::open(&path) {
                Ok(file) => BufReader::new(file),
                Err(error) => panic!("{}", error),
            };

            for line in reader.lines() {
                match line {
                    Ok(line) => self::read_mailmap_line(line.as_slice()),
                    Err(x) => break
                };
            }
        }

        struct Author {
            name: String,
            email: String
        }

        struct MailmapLine {
            new_author: Author,
            old_author: Author
        }

        fn read_mailmap_line(line: &str) -> Option<MailmapLine> {
            if line.len() > 0 && line.char_at(0) != '#' {
                parse_mailmap_name_email(line);
            }

            None
        }

        fn parse_mailmap_name_email(line: &str) -> Option<Author> {
            let left = match line.find('<') {
                Some(i) => i,
                None => -1
            };

            let right = match line.find('>') {
                Some(i) => i,
                None => -1
            };

            if left == -1 || right == -1 {
                return None;
            }

            if left > right {
                return None;
            }

            if left+1 == right {
                return None;
            }

            Some(Author {
                name: String::from_str(UnicodeStr::trim(&line[..left])),
                email: String::from_str(UnicodeStr::trim(&line[left..right]))
            })

        }

        #[test]
        fn test_parse_name_email() {
            assert!(self::parse_mailmap_name_email("name <email>").is_some());
            assert!(self::parse_mailmap_name_email("name <>").is_none());
            assert!(self::parse_mailmap_name_email(">").is_none());
            assert!(self::parse_mailmap_name_email("<").is_none());
            assert!(self::parse_mailmap_name_email("><").is_none());
            assert!(self::parse_mailmap_name_email("").is_none());
            assert!(self::parse_mailmap_name_email("<email>").is_some());
        }

    }
}

#[cfg(test)]
mod tests {
    use std::old_io::TempDir;
    use git2::Repository;
    use gitstat::run;

    // fn repo_init() -> (TempDir, Repository) {
    //     let td = TempDir::new("test").unwrap();
    //     let repo = Repository::init(td.path()).unwrap();
    //     {
    //         let mut config = repo.config().unwrap();
    //         config.set_str("user.name", "name").unwrap();
    //         config.set_str("user.email", "email").unwrap();
    //         let mut index = repo.index().unwrap();
    //         let id = index.write_tree().unwrap();
    //
    //         let tree = repo.find_tree(id).unwrap();
    //         let sig = repo.signature().unwrap();
    //         repo.commit(Some("HEAD"), &sig, &sig, "initial", &tree, []).unwrap();
    //     }
    //     (td, repo)
    // }
    //
    // #[test]
    // fn smoke_run() {
    //     let (td, _repo) = self::repo_init();
    //     let path = td.unwrap();
    //     let result = run(&path);
    //     assert!(result.is_ok());
    //     assert_eq!(result.unwrap().total, 1);
    // }
}
<commit_msg>using try! + revwalk<commit_after>#![feature(os)]
#![feature(core)]
#![feature(collections)]
#![feature(unicode)]
#![feature(str_char)]

extern crate git2;
extern crate chrono;
extern crate getopts;
extern crate unicode;

use getopts::Options;
use std::os;
use std::path::Path;

#[cfg(not(test))]
fn main() {
    let args: Vec<String> = os::args();
    let program = args[0].clone();

    let mut opts = Options::new();
    opts.optflag("h", "help", "print this help menu");

    let matches = match opts.parse(args.tail()) {
        Ok(m) => { m }
        Err(f) => { panic!(f.to_string()) }
    };

    if matches.opt_present("h") {
        print_usage(program.as_slice(), opts);
        return;
    }

    let input = if !matches.free.is_empty() {
        println!("count of opts: {}", matches.free.len());
        matches.free[0].clone()
    } else {
        print_usage(program.as_slice(), opts);
        return;
    };

    let path = Path::new(input.as_slice());

    // println!("Total count of commits in '{}' is: {}", path.display(), stat.total);
    match gitstat::run(&path) {
        Ok(()) => {}
        Err(e) => println!("error: {}", e),
    }
}

fn print_usage(program: &str, opts: Options) {
    let brief = format!("Usage: {} [options]", program);
    print!("{}", opts.usage(brief.as_slice()));
}

pub struct Stat {
    total: usize,
}

mod gitstat {
    use git2::{Repository,Commit,Oid,Signature,Time,Error};
    use std::collections::HashMap;
    use std::collections::hash_map::Entry;
    use std::sync::{Mutex, Arc};
    use std::path::Path;
    use chrono::datetime::DateTime;
    use chrono::naive::datetime::NaiveDateTime;
    use chrono::{Timelike, Datelike};
    use std::cmp;
    use Stat;

    pub fn run(path: &Path) -> Result<(), Error> {
        let repo = try!(Repository::open(path));

        let authors: HashMap<String, usize> = try!(self::get_authors(&repo));

        // iterate over everything.
        for (name, count) in authors.iter() {
            println!("{}: {}", *name, *count);
        }

        Ok(())
    }

    /// Helper method for gets HEAD commit of given git repository
    fn get_head_commit(repo: &Box<Repository>) -> Option<Commit> {
        repo.head().ok()
            .and_then(|h| h.target())
            .and_then(|oid| repo.find_commit(oid).ok())
    }

    fn get_authors(repo: &Repository) -> Result<HashMap<String, usize>, Error> {
        let mut heatmap = [[0u32; 24]; 7];
        let mut authors: HashMap<String, usize> = HashMap::new();
        let mut revwalk = try!(repo.revwalk());

        revwalk.push_head();
        // let mutex = Mutex::new(repo);

        // mailmap::read_mailmap_file(path);
        for oid in revwalk {
            let commit = try!(repo.find_commit(oid));

            let uniq_name: String = get_uniq_name(&commit.author());
            let (weekday, hour) = get_heatmat_coords(&commit.time());

            heatmap[weekday as usize][hour as usize] += 1;
            // println!("{} {}", time.seconds(), time.offset_minutes());

            match authors.entry(uniq_name) {
                Entry::Vacant(entry) => entry.insert(1),
                Entry::Occupied(mut entry) => {
                    *entry.get_mut() += 1;
                    entry.into_mut()
                }
            };
        }

        // find max
        let mut max: u32 = 0;
        for hours in heatmap.iter() {
            for count in hours.iter() {
                max = cmp::max(*count, max);
            }
        }

        let arts = ['.', '▪', '◾', '◼', '⬛'];

        for hours in heatmap.iter() {
            for count in hours.iter() {
                print!("{:4}", arts[(count % arts.len() as u32) as usize]);
                // print!("{:4}", count);
            }
            println!("");
        }

        Ok(authors)
    }

    fn get_uniq_name(author: &Signature) -> String {
        format!("{} <{}>", author.name().unwrap(), author.email().unwrap())
    }

    fn get_heatmat_coords(time: &Time) -> (u32, u32) {
        let timestamp: NaiveDateTime = NaiveDateTime::from_timestamp(time.seconds(), 0);

        (timestamp.weekday().num_days_from_monday(), timestamp.hour())
    }

    /// Module `mailmap` which implement logic for parse .mailmap files
    mod mailmap {

        use std::env;
        use std::fs::File;
        use std::io::{BufReader, BufRead};
        use std::string::String;
        use std::path::Path;
        use unicode::str::UnicodeStr;

        pub fn read_mailmap_file(basedir: &Path) {
            // get full path to .mailmap
            let path = match env::current_dir() {
                Ok(path) => path.join(".mailmap"),
                Err(error) => panic!("{}", error),
            };

            // create BufReader instance for .mailmap file
            let mut reader = match File::open(&path) {
                Ok(file) => BufReader::new(file),
                Err(error) => panic!("{}", error),
            };

            for line in reader.lines() {
                match line {
                    Ok(line) => self::read_mailmap_line(line.as_slice()),
                    Err(x) => break
                };
            }
        }

        struct Author {
            name: String,
            email: String
        }

        struct MailmapLine {
            new_author: Author,
            old_author: Author
        }

        fn read_mailmap_line(line: &str) -> Option<MailmapLine> {
            if line.len() > 0 && line.char_at(0) != '#' {
                parse_mailmap_name_email(line);
            }

            None
        }

        fn parse_mailmap_name_email(line: &str) -> Option<Author> {
            let left = match line.find('<') {
                Some(i) => i,
                None => -1
            };

            let right = match line.find('>') {
                Some(i) => i,
                None => -1
            };

            if left == -1 || right == -1 {
                return None;
            }

            if left > right {
                return None;
            }

            if left+1 == right {
                return None;
            }

            Some(Author {
                name: String::from_str(UnicodeStr::trim(&line[..left])),
                email: String::from_str(UnicodeStr::trim(&line[left..right]))
            })

        }

        #[test]
        fn test_parse_name_email() {
            assert!(self::parse_mailmap_name_email("name <email>").is_some());
            assert!(self::parse_mailmap_name_email("name <>").is_none());
            assert!(self::parse_mailmap_name_email(">").is_none());
            assert!(self::parse_mailmap_name_email("<").is_none());
            assert!(self::parse_mailmap_name_email("><").is_none());
            assert!(self::parse_mailmap_name_email("").is_none());
            assert!(self::parse_mailmap_name_email("<email>").is_some());
        }

    }
}

#[cfg(test)]
mod tests {
    use std::old_io::TempDir;
    use git2::Repository;
    use gitstat::run;

    // fn repo_init() -> (TempDir, Repository) {
    //     let td = TempDir::new("test").unwrap();
    //     let repo = Repository::init(td.path()).unwrap();
    //     {
    //         let mut config = repo.config().unwrap();
    //         config.set_str("user.name", "name").unwrap();
    //         config.set_str("user.email", "email").unwrap();
    //         let mut index = repo.index().unwrap();
    //         let id = index.write_tree().unwrap();
    //
    //         let tree = repo.find_tree(id).unwrap();
    //         let sig = repo.signature().unwrap();
    //         repo.commit(Some("HEAD"), &sig, &sig, "initial", &tree, []).unwrap();
    //     }
    //     (td, repo)
    // }
    //
    // #[test]
    // fn smoke_run() {
    //     let (td, _repo) = self::repo_init();
    //     let path = td.unwrap();
    //     let result = run(&path);
    //     assert!(result.is_ok());
    //     assert_eq!(result.unwrap().total, 1);
    // }
}
<|endoftext|>


<commit_before>extern crate clap;
use clap::{Arg, App, AppSettings};
use slog::{Logger, LevelFilter, Level, DrainExt};
use yaml_rust::YamlLoader;
use std::io::prelude::*;
use std::fs::File;
use yaml_rust::Yaml;
use model::*;
use std::path::Path;
use std::fs;
use std::error::Error;

#[macro_use]
extern crate slog;
extern crate slog_term;
extern crate slog_stdlog;
extern crate yaml_rust;
extern crate crypto;

#[cfg(test)]
extern crate spectral;

mod model;
mod parser;
mod checksum;

fn main() {
  let matches: clap::ArgMatches = create_app().get_matches();
  let stream = slog_term::streamer().full().build();
  let verbose: bool = matches.is_present("verbose");
  let force: bool = matches.is_present("force");
  let log = if verbose {
    slog::Logger::root(stream.fuse(), o!())
  } else {
    slog::Logger::root(LevelFilter::new(stream, Level::Info).fuse(), o!())
  };

  let config_file = matches.value_of("config_file").unwrap();
  info!(log, "Starting engine"; "config_file" => config_file);

  let config = match load_config_file(config_file) {
    Ok(content) => content,
    Err(e) => {
      error!(log, "Failed to load config file."; "error" => e.description());
      panic!(1)
    }
  };

  let yaml_documents = match YamlLoader::load_from_str(&config) {
    Ok(yaml) => yaml,
    Err(e) => {
      error!(log, "Failed to parse config file."; "error" => e.description());
      panic!(2)
    }
  };
  let yaml_config = &yaml_documents[0];
  let dot_files: &Yaml = &yaml_config["files"];
  if dot_files.is_badvalue() {
    warn!(log, "Empty files list");
  }

  info!(log, "Parsed config file. Liftoff!");
  for dot_file in parser::parse_dot_files(&log, dot_files) {
    process_dot_file(&log, dot_file, force);
  }
}

fn process_dot_file(log: &Logger, dot_file: DotFile, force: bool) {
  let log = &log.new(o!("target" => dot_file.target.clone(), "source" => dot_file.source.clone(), "type" => format!("{:?}", dot_file.dot_file_type)));
  info!(log, "Process entry");
  let source_path = Path::new(&dot_file.source);
  let target_path = Path::new(&dot_file.target);
  if !source_path.exists() {
    warn!(log, "Source path does not exist");
    return;
  }
  match dot_file.dot_file_type {
    DotFileType::LINK => process_link(log, source_path, target_path, force),
    DotFileType::COPY => process_copy(log, source_path, target_path, force)
  }
}

fn process_copy(log: &Logger, source_path: &Path, target_path: &Path, force: bool) {
  match has_same_content(log, source_path, target_path) {
    Ok(true) => {},
    Ok(false) => {},
    Err(e) => error!(log, "Failed to copy dotfile"; "error" => e.description())
  }
}

fn has_same_content(log: &Logger, source_path: &Path, target_path: &Path) -> Result<bool, std::io::Error> {
  if !target_path.exists() {
    Ok(false)
  } else if target_path.is_dir() || source_path.is_dir() {
    Ok(false) //TODO: ???
  } else {
    let source_hash = try!(checksum::hash(source_path));
    let target_hash = try!(checksum::hash(source_path));
    debug!(log, "Hashed files"; "target_hash" => target_hash, "source_hash" => source_hash);
    Ok(true)
  }
}

fn process_link(log: &Logger, source_path: &Path, target_path: &Path, force: bool) {
  match already_linked(source_path, target_path) {
    Ok(true) => info!(log, "Link already exists"),
    Ok(false) => {
      if !force && target_path.exists() {
        error!(log, "Target exists but does not point to source")
      } else {
        let result = link_dot_file(source_path, target_path);
        match result {
          Err(e) => error!(log, "Failed to create link"; "error" => e.description()),
          Ok(_) => info!(log, "Link created successfully"),
        }
      }
    }
    Err(e) => error!(log, "Failed to check link existence"; "error" => e.description()),
  }
}

fn already_linked(source: &Path, target: &Path) -> Result<bool, std::io::Error> {
  if target.exists() {
    let canonicalized_target = try!(fs::canonicalize(target));
    let canonicalized_source = try!(fs::canonicalize(source));
    Ok(canonicalized_source == canonicalized_target)
  } else {
    Ok(false)
  }
}

fn link_dot_file(source: &Path, target: &Path) -> Result<(), std::io::Error> {
  if let Some(parent) = target.parent() {
    try!(fs::create_dir_all(parent));
  }
  if target.exists() {
    if target.is_file() {
      try!(fs::remove_file(target))
    } else if target.is_dir() {
      try!(fs::remove_dir_all(target))
    }
  }
  let canonicalized_source = try!(fs::canonicalize(source));
  try!(std::os::unix::fs::symlink(canonicalized_source, target));
  Ok(())
}

fn load_config_file(file: &str) -> Result<String, std::io::Error> {
  let mut file = try!(File::open(file));
  let mut content = String::new();
  try!(file.read_to_string(&mut content));
  return Ok(content);
}

fn create_app<'a>() -> App<'a, 'a> {
  App::new("dotcopter")
    .version("0.1")
    .setting(AppSettings::ColoredHelp)
    .author("Patrick Haun <bomgar85@googlemail.com>")
    .about("manages dotfiles installation")
    .arg(Arg::with_name("force").short("f").long("force").takes_value(false))
    .arg(Arg::with_name("verbose")
           .long("verbose")
           .short("v")
           .help("debug output")
           .required(false)
           .takes_value(false))
    .arg(Arg::with_name("config_file").required(true))
}
<commit_msg>finish copy implementation<commit_after>extern crate clap;
use clap::{Arg, App, AppSettings};
use slog::{Logger, LevelFilter, Level, DrainExt};
use yaml_rust::YamlLoader;
use std::io::prelude::*;
use std::fs::File;
use yaml_rust::Yaml;
use model::*;
use std::path::Path;
use std::fs;
use std::error::Error;

#[macro_use]
extern crate slog;
extern crate slog_term;
extern crate slog_stdlog;
extern crate yaml_rust;
extern crate crypto;

#[cfg(test)]
extern crate spectral;

mod model;
mod parser;
mod checksum;

fn main() {
  let matches: clap::ArgMatches = create_app().get_matches();
  let stream = slog_term::streamer().full().build();
  let verbose: bool = matches.is_present("verbose");
  let force: bool = matches.is_present("force");
  let log = if verbose {
    slog::Logger::root(stream.fuse(), o!())
  } else {
    slog::Logger::root(LevelFilter::new(stream, Level::Info).fuse(), o!())
  };

  let config_file = matches.value_of("config_file").unwrap();
  info!(log, "Starting engine"; "config_file" => config_file);

  let config = match load_config_file(config_file) {
    Ok(content) => content,
    Err(e) => {
      error!(log, "Failed to load config file."; "error" => e.description());
      panic!(1)
    }
  };

  let yaml_documents = match YamlLoader::load_from_str(&config) {
    Ok(yaml) => yaml,
    Err(e) => {
      error!(log, "Failed to parse config file."; "error" => e.description());
      panic!(2)
    }
  };
  let yaml_config = &yaml_documents[0];
  let dot_files: &Yaml = &yaml_config["files"];
  if dot_files.is_badvalue() {
    warn!(log, "Empty files list");
  }

  info!(log, "Parsed config file. Liftoff!");
  for dot_file in parser::parse_dot_files(&log, dot_files) {
    process_dot_file(&log, dot_file, force);
  }
}

fn process_dot_file(log: &Logger, dot_file: DotFile, force: bool) {
  let log = &log.new(o!("target" => dot_file.target.clone(), "source" => dot_file.source.clone(), "type" => format!("{:?}", dot_file.dot_file_type)));
  debug!(log, "Process entry");
  let source_path = Path::new(&dot_file.source);
  let target_path = Path::new(&dot_file.target);
  if !source_path.exists() {
    warn!(log, "Source path does not exist");
    return;
  }
  match dot_file.dot_file_type {
    DotFileType::LINK => process_link(log, source_path, target_path, force),
    DotFileType::COPY => process_copy(log, source_path, target_path, force)
  }
}

fn process_copy(log: &Logger, source_path: &Path, target_path: &Path, force: bool) {
  match has_same_content(log, source_path, target_path) {
    Ok(true) => info!(log, "File already there"),
    Ok(false) => {
      if !force && target_path.exists() {
        error!(log, "Target already exists but has different content.");
        return;
      }
      match copy_dot_file(source_path, target_path) {
        Ok(_) => info!(log, "Copied file successfully"),
        Err(e) => error!(log, "Failed to copy file"; "error" => e.description())
      }
    },
    Err(e) => error!(log, "Failed to copy dotfile"; "error" => e.description())
  }
}

fn copy_dot_file(source: &Path, target: &Path) -> Result<(), std::io::Error> {
  if let Some(parent) = target.parent() {
    try!(fs::create_dir_all(parent));
  }
  if target.exists() {
    if target.is_file() {
      try!(fs::remove_file(target))
    } else if target.is_dir() {
      try!(fs::remove_dir_all(target))
    }
  }
  try!(fs::copy(source, target));
  Ok(())
}

fn has_same_content(log: &Logger, source: &Path, target: &Path) -> Result<bool, std::io::Error> {
  if !target.exists() {
    Ok(false)
  } else if target.is_dir() || source.is_dir() {
    Ok(false) //TODO: ???
  } else {
    let source_hash = try!(checksum::hash(source));
    let target_hash = try!(checksum::hash(target));
    debug!(log, "Hashed files"; "target_hash" => target_hash, "source_hash" => source_hash);
    Ok(source_hash == target_hash)
  }
}

fn process_link(log: &Logger, source_path: &Path, target_path: &Path, force: bool) {
  match already_linked(source_path, target_path) {
    Ok(true) => info!(log, "Link already exists"),
    Ok(false) => {
      if !force && target_path.exists() {
        error!(log, "Target exists but does not point to source")
      } else {
        let result = link_dot_file(source_path, target_path);
        match result {
          Err(e) => error!(log, "Failed to create link"; "error" => e.description()),
          Ok(_) => info!(log, "Link created successfully"),
        }
      }
    }
    Err(e) => error!(log, "Failed to check link existence"; "error" => e.description()),
  }
}

fn already_linked(source: &Path, target: &Path) -> Result<bool, std::io::Error> {
  if target.exists() {
    let canonicalized_target = try!(fs::canonicalize(target));
    let canonicalized_source = try!(fs::canonicalize(source));
    Ok(canonicalized_source == canonicalized_target)
  } else {
    Ok(false)
  }
}

fn link_dot_file(source: &Path, target: &Path) -> Result<(), std::io::Error> {
  if let Some(parent) = target.parent() {
    try!(fs::create_dir_all(parent));
  }
  if target.exists() {
    if target.is_file() {
      try!(fs::remove_file(target))
    } else if target.is_dir() {
      try!(fs::remove_dir_all(target))
    }
  }
  let canonicalized_source = try!(fs::canonicalize(source));
  try!(std::os::unix::fs::symlink(canonicalized_source, target));
  Ok(())
}

fn load_config_file(file: &str) -> Result<String, std::io::Error> {
  let mut file = try!(File::open(file));
  let mut content = String::new();
  try!(file.read_to_string(&mut content));
  return Ok(content);
}

fn create_app<'a>() -> App<'a, 'a> {
  App::new("dotcopter")
    .version("0.1")
    .setting(AppSettings::ColoredHelp)
    .author("Patrick Haun <bomgar85@googlemail.com>")
    .about("manages dotfiles installation")
    .arg(Arg::with_name("force").short("f").long("force").takes_value(false))
    .arg(Arg::with_name("verbose")
           .long("verbose")
           .short("v")
           .help("debug output")
           .required(false)
           .takes_value(false))
    .arg(Arg::with_name("config_file").required(true))
}
<|endoftext|>