<commit_before>extern crate rand;
extern crate djinn;
extern crate redis;
extern crate rustc_serialize;

use rand::Rng;
use redis::Client;
use djinn::{Agent, Manager, Simulation, Population, Updates, Redis, run};
use rand::distributions::{Weighted, WeightedChoice, IndependentSample};
use std::collections::HashMap;

// when two people share the exact same polarity on an opinion,
// this is how much their trust improves by
const MAX_POS_TRUST_SHIFT: f64 = 1.;

// if two people have a polarity within +/- this value,
// their trust improves
const MAX_POS_TRUST_SHIFT_RANGE: f64 = 4.;

// when an opinion shifts, it shifts by this proportion of
// the opinion difference
const OPINION_SHIFT_PROPORTION: f64 = 0.1;

#[derive(RustcDecodable, RustcEncodable, Debug, PartialEq, Clone)]
struct Opinion {
    polarity: i32,
    priority: u32,
}

#[derive(RustcDecodable, RustcEncodable, Debug, PartialEq, Clone)]
struct Person {
    opinions: Vec<Opinion>,
    medias: HashMap<u64, u32>,
    friends: HashMap<u64, u32>,
}

impl Person {
    pub fn new(opinions: Vec<Opinion>) -> Person {
        Person {
            opinions: opinions,
            medias: HashMap::new(),
            friends: HashMap::new(),
        }
    }

    /// Select a random opinion weighted by priority.
    fn rand_opinion_idx(&self, mut rng: &mut Rng, opinions: &Vec<Opinion>) -> usize {
        let mut items: Vec<Weighted<usize>> = opinions.iter()
            .enumerate()
            .map(|(i, o)| {
                Weighted {
                    item: i,
                    weight: o.priority,
                }
            })
            .collect();
        let wc = WeightedChoice::new(&mut items);
        wc.ind_sample(&mut rng).clone()
    }

    /// Select a random edge by weight.
    fn rand_edge(&self, mut rng: &mut Rng, edges: &HashMap<u64, u32>) -> u64 {
        let mut items: Vec<Weighted<u64>> = edges.iter()
            .map(|(id, weight)| {
                Weighted {
                    item: *id,
                    weight: *weight,
                }
            })
            .collect();
        let wc = WeightedChoice::new(&mut items);
        wc.ind_sample(&mut rng)
    }

    /// Trust is computed from alignment of opinion polarities,
    /// weighted by the importance/priority of those opinions
    /// from the perspective of the subject.
    fn trust_from_opinions(&self, opinions: &Vec<Opinion>) -> u32 {
        self.opinions
            .iter()
            .zip(opinions.iter())
            .fold(0, |acc, (o1, o2)| acc + self.trust_from_opinion(o1, o2))
    }

    fn trust_from_opinion(&self, o1: &Opinion, o2: &Opinion) -> u32 {
        let dist = (o1.polarity - o2.polarity).abs();
        let x = ((2 - dist) as f64) / 1.;
        ((x - 1.) * (o1.priority as f64)).round() as u32
    }

    /// TODO atm only considering a shift _towards_ the other person's opinion,
    /// but totally possible that encountering a divergent opinion
    /// causes one to double-down on their own opinion.
    /// TODO atm this is only a one-way exchange, totally possible to influence the
    /// other person's opinion too.
    fn be_influenced(&self, op_idx: usize, op1: &Opinion, op2: &Opinion) -> PersonUpdate {
        let diff = op2.polarity - op1.polarity;
        let shift = ((diff as f64) * OPINION_SHIFT_PROPORTION).round() as i32;
        PersonUpdate::OpinionShift {
            idx: op_idx,
            polarity: shift,
        }
    }

    /// Based on hearing this person's opinion, adjust trust.
    /// The lower distance between their opinions, the more they trust each other.
    fn trust_shift(&self, op1: &Opinion, op2: &Opinion) -> i32 {
        let dist = (op1.polarity - op2.polarity).abs();
        let trust_shift = -((dist as f64) / MAX_POS_TRUST_SHIFT_RANGE).powi(2) +
                          MAX_POS_TRUST_SHIFT;
        trust_shift.round() as i32
    }
}

#[derive(RustcDecodable, RustcEncodable, Debug, PartialEq, Clone)]
enum EdgeType {
    Media,
    Friend,
}

#[derive(RustcDecodable, RustcEncodable, Debug, PartialEq, Clone)]
struct Media {
    opinions: Vec<Opinion>,
}

#[derive(RustcDecodable, RustcEncodable, Debug, PartialEq, Clone)]
enum State {
    Person(Person),
    Media(Media),
}

#[derive(RustcDecodable, RustcEncodable, Debug, PartialEq, Clone)]
enum PersonUpdate {
    OpinionShift { idx: usize, polarity: i32 },
    TrustShift {
        id: u64,
        shift: i32,
        edge_type: EdgeType,
    },
}

#[derive(RustcDecodable, RustcEncodable, Debug, PartialEq, Clone)]
enum MediaUpdate {
    OpinionShift {
        idx: usize,
        polarity: i32,
        priority: i32,
    },
}

#[derive(RustcDecodable, RustcEncodable, Debug, PartialEq, Clone)]
enum Update {
    Person(PersonUpdate),
    Media(MediaUpdate),
}

#[derive(RustcDecodable, RustcEncodable, Debug, PartialEq, Clone)]
struct World {
    weather: String,
}

#[derive(Clone)]
struct OpinionDynamicsSim;

impl OpinionDynamicsSim {
    fn decide_person<R: Redis>(&self,
                               id: u64,
                               person: &Person,
                               pop: &Population<Self, R>,
                               updates: &mut Updates<Self>)
                               -> () {
        let mut rng = rand::weak_rng();

        // talk to a person or consume media?
        let other = if rng.gen::<f64>() < 0.5 {
            // choose a media.
            // the less media this person is familiar with,
            // the more likely they will encounter a random one.
            // otherwise, they choose one with probability based on how much they trust it.
            let p_rand_media = (person.medias.len() as f64) / 2.; // TODO denom should be a config val
            if rng.gen::<f64>() < p_rand_media {
                pop.random("media")
            } else {
                let id = person.rand_edge(&mut rng, &person.medias);
                pop.get_agent(id).unwrap()
            }
        } else {
            // choose a person to talk to.
            let p_rand_person = (person.friends.len() as f64) / 2.; // TODO denom should be a config val
            if rng.gen::<f64>() < p_rand_person {
                pop.random("person") // TODO prob shouldnt be themselves
            } else {
                let id = person.rand_edge(&mut rng, &person.friends);
                pop.get_agent(id).unwrap()
            }
        };
        match other.state {
            State::Person(ref p) => {
                let op_idx = person.rand_opinion_idx(&mut rng, &p.opinions);
                let ref op1 = person.opinions[op_idx];
                let ref op2 = p.opinions[op_idx];

                // naively bootstrap trust for new person as 0
                let trust = match person.friends.get(&other.id) {
                    Some(t) => *t,
                    None => 0,
                };
                let p_opinion_shift = ((trust as f64) + 0.01) / 100.;
                if rng.gen::<f64>() < p_opinion_shift {
                    updates.queue(id, Update::Person(person.be_influenced(op_idx, op1, op2)));
                }

                updates.queue(id,
                              Update::Person(PersonUpdate::TrustShift {
                                  id: other.id,
                                  shift: person.trust_shift(op1, op2),
                                  edge_type: EdgeType::Friend,
                              }))
            }
            State::Media(ref m) => {
                let op_idx = person.rand_opinion_idx(&mut rng, &m.opinions);
                let ref op1 = person.opinions[op_idx];
                let ref op2 = m.opinions[op_idx];

                // naively bootstrap trust for new media as 0
                let trust = match person.medias.get(&other.id) {
                    Some(t) => *t,
                    None => 0,
                };

                let p_opinion_shift = ((trust as f64) + 0.01) / 100.;
                if rng.gen::<f64>() < p_opinion_shift {
                    updates.queue(id, Update::Person(person.be_influenced(op_idx, op1, op2)));
                }

                updates.queue(id,
                              Update::Person(PersonUpdate::TrustShift {
                                  id: other.id,
                                  shift: person.trust_shift(op1, op2),
                                  edge_type: EdgeType::Media,
                              }))
            }
        }
    }

    fn update_person(&self, mut person: &mut Person, updates: Vec<Update>) -> bool {
        let mut updated = false;
        for update in updates {
            match update {
                Update::Person(u) => {
                    match u {
                        PersonUpdate::OpinionShift { idx, polarity } => {
                            let ref mut op = person.opinions[idx];
                            op.polarity += polarity;
                        }
                        PersonUpdate::TrustShift { id, shift, edge_type } => {
                            match edge_type {
                                EdgeType::Friend => {
                                    let trust = *person.friends.entry(id).or_insert(0) as i32;
                                    person.friends.insert(id, (trust + shift) as u32);
                                }
                                EdgeType::Media => {
                                    let trust = *person.medias.entry(id).or_insert(0) as i32;
                                    person.medias.insert(id, (trust + shift) as u32);
                                }
                            }
                        }
                    }
                }
                _ => (),
            }
        }
        updated
    }
}

impl Simulation for OpinionDynamicsSim {
    type State = State;
    type Update = Update;
    type World = World;

    fn decide<R: Redis>(&self,
                        agent: &Agent<Self::State>,
                        world: &Self::World,
                        pop: &Population<Self, R>,
                        updates: &mut Updates<Self>)
                        -> () {
        match agent.state {
            State::Person(ref p) => {
                self.decide_person(agent.id, p, pop, updates);
            }
            State::Media(ref m) => {
                // TODO
            }
        }
    }

    fn update(&self, mut state: &mut Self::State, updates: Vec<Self::Update>) -> bool {
        // TODO
        false
    }

    fn on_spawns<R: Redis>(&self,
                           agents: Vec<Agent<Self::State>>,
                           population: &Population<Self, R>)
                           -> () {
        // index newly created media/people
        // so we can sample them later
        let mut media = Vec::new();
        let mut people = Vec::new();
        let _: Vec<()> = agents.iter()
            .map(|a| {
                match a.state {
                    State::Person(_) => people.push(a.id),
                    State::Media(_) => media.push(a.id),
                }
            })
            .collect();
        let _: () = population.indexes("media", media);
        let _: () = population.indexes("people", people);
    }
}

// TODO
// person step:
//  - sample a person to talk to, based on trust. or encounter random.
//  - read a story from media, based on opinion alignment and importance
//  - talk to person
//  - change opinion based on media
//  - change opinion based on talking to person
//

fn main() {
    // TODO
    // - bootstrap social network
    // - people more likely talk to likeminded friends
    // - randomly meet new people

    let sim = OpinionDynamicsSim {};

    // TODO
    let world = World { weather: "sunny".to_string() };

    // Setup the manager
    let addr = "redis://127.0.0.1/";
    let pop_client = Client::open(addr).unwrap();
    let mut manager = Manager::new(addr, pop_client, sim.clone());

    let mut medias = vec![Media {
                              opinions: vec![Opinion {
                                                 polarity: -1,
                                                 priority: 5,
                                             },
                                             Opinion {
                                                 polarity: -5,
                                                 priority: 1,
                                             }],
                          },
                          Media {
                              opinions: vec![Opinion {
                                                 polarity: 1,
                                                 priority: 8,
                                             },
                                             Opinion {
                                                 polarity: 2,
                                                 priority: 4,
                                             }],
                          }];
    let media_ids = manager.spawns(medias.drain(..).map(|m| State::Media(m)).collect());

    let mut people = vec![Person::new(vec![Opinion {
                                               polarity: 1,
                                               priority: 1,
                                           },
                                           Opinion {
                                               polarity: 0,
                                               priority: 0,
                                           }]),
                          Person::new(vec![Opinion {
                                               polarity: -1,
                                               priority: 1,
                                           },
                                           Opinion {
                                               polarity: 1,
                                               priority: 0,
                                           }])];
    let people_ids = manager.spawns(people.drain(..).map(|m| State::Person(m)).collect());

    // run(sim, world, manager, 4, 10);
}
<commit_msg>added media update methods<commit_after>extern crate rand;
extern crate djinn;
extern crate redis;
extern crate rustc_serialize;

use rand::Rng;
use redis::Client;
use djinn::{Agent, Manager, Simulation, Population, Updates, Redis, run};
use rand::distributions::{Weighted, WeightedChoice, IndependentSample};
use std::collections::HashMap;

// when two people share the exact same polarity on an opinion,
// this is how much their trust improves by
const MAX_POS_TRUST_SHIFT: f64 = 1.;

// if two people have a polarity within +/- this value,
// their trust improves
const MAX_POS_TRUST_SHIFT_RANGE: f64 = 4.;

// when an opinion shifts, it shifts by this proportion of
// the opinion difference
const OPINION_SHIFT_PROPORTION: f64 = 0.1;

#[derive(RustcDecodable, RustcEncodable, Debug, PartialEq, Clone)]
struct Opinion {
    polarity: i32,
    priority: u32,
}

#[derive(RustcDecodable, RustcEncodable, Debug, PartialEq, Clone)]
struct Person {
    opinions: Vec<Opinion>,
    medias: HashMap<u64, u32>,
    friends: HashMap<u64, u32>,
}

impl Person {
    pub fn new(opinions: Vec<Opinion>) -> Person {
        Person {
            opinions: opinions,
            medias: HashMap::new(),
            friends: HashMap::new(),
        }
    }

    /// Select a random opinion weighted by personal priority.
    fn rand_opinion_idx(&self, mut rng: &mut Rng, opinions: &Vec<Opinion>) -> usize {
        let mut items: Vec<Weighted<usize>> = opinions.iter()
            .enumerate()
            .map(|(i, o)| {
                Weighted {
                    item: i,
                    weight: self.opinions[i].priority,
                }
            })
            .collect();
        let wc = WeightedChoice::new(&mut items);
        wc.ind_sample(&mut rng).clone()
    }

    /// Select a random edge by weight.
    fn rand_edge(&self, mut rng: &mut Rng, edges: &HashMap<u64, u32>) -> u64 {
        let mut items: Vec<Weighted<u64>> = edges.iter()
            .map(|(id, weight)| {
                Weighted {
                    item: *id,
                    weight: *weight,
                }
            })
            .collect();
        let wc = WeightedChoice::new(&mut items);
        wc.ind_sample(&mut rng)
    }

    /// Trust is computed from alignment of opinion polarities,
    /// weighted by the importance/priority of those opinions
    /// from the perspective of the subject.
    fn trust_from_opinions(&self, opinions: &Vec<Opinion>) -> u32 {
        self.opinions
            .iter()
            .zip(opinions.iter())
            .fold(0, |acc, (o1, o2)| acc + self.trust_from_opinion(o1, o2))
    }

    fn trust_from_opinion(&self, o1: &Opinion, o2: &Opinion) -> u32 {
        let dist = (o1.polarity - o2.polarity).abs();
        let x = ((2 - dist) as f64) / 1.;
        ((x - 1.) * (o1.priority as f64)).round() as u32
    }

    /// TODO atm only considering a shift _towards_ the other person's opinion,
    /// but totally possible that encountering a divergent opinion
    /// causes one to double-down on their own opinion.
    /// TODO atm this is only a one-way exchange, totally possible to influence the
    /// other person's opinion too.
    fn be_influenced(&self, op_idx: usize, op1: &Opinion, op2: &Opinion) -> PersonUpdate {
        let diff = op2.polarity - op1.polarity;
        let shift = ((diff as f64) * OPINION_SHIFT_PROPORTION).round() as i32;
        PersonUpdate::OpinionShift {
            idx: op_idx,
            polarity: shift,
        }
    }

    /// Based on hearing this person's opinion, adjust trust.
    /// The lower distance between their opinions, the more they trust each other.
    fn trust_shift(&self, op1: &Opinion, op2: &Opinion) -> i32 {
        let dist = (op1.polarity - op2.polarity).abs();
        let trust_shift = -((dist as f64) / MAX_POS_TRUST_SHIFT_RANGE).powi(2) +
                          MAX_POS_TRUST_SHIFT;
        trust_shift.round() as i32
    }
}

#[derive(RustcDecodable, RustcEncodable, Debug, PartialEq, Clone)]
enum EdgeType {
    Media,
    Friend,
}

#[derive(RustcDecodable, RustcEncodable, Debug, PartialEq, Clone)]
struct Media {
    opinions: Vec<Opinion>,
}

#[derive(RustcDecodable, RustcEncodable, Debug, PartialEq, Clone)]
enum State {
    Person(Person),
    Media(Media),
}

#[derive(RustcDecodable, RustcEncodable, Debug, PartialEq, Clone)]
enum PersonUpdate {
    OpinionShift { idx: usize, polarity: i32 },
    TrustShift {
        id: u64,
        shift: i32,
        edge_type: EdgeType,
    },
}

#[derive(RustcDecodable, RustcEncodable, Debug, PartialEq, Clone)]
enum MediaUpdate {
    Click { idx: usize, polarity: i32 },
}

#[derive(RustcDecodable, RustcEncodable, Debug, PartialEq, Clone)]
enum Update {
    Person(PersonUpdate),
    Media(MediaUpdate),
}

#[derive(RustcDecodable, RustcEncodable, Debug, PartialEq, Clone)]
struct World {
    weather: String,
}

#[derive(Clone)]
struct OpinionDynamicsSim;

impl OpinionDynamicsSim {
    fn decide_media<R: Redis>(&self,
                              id: u64,
                              media: &Media,
                              pop: &Population<Self, R>,
                              updates: &mut Updates<Self>)
                              -> () {
        // media's not really doing anything atm
    }

    fn decide_person<R: Redis>(&self,
                               id: u64,
                               person: &Person,
                               pop: &Population<Self, R>,
                               updates: &mut Updates<Self>)
                               -> () {
        let mut rng = rand::weak_rng();

        // talk to a person or consume media?
        let other = if rng.gen::<f64>() < 0.5 {
            // choose a media.
            // the less media this person is familiar with,
            // the more likely they will encounter a random one.
            // otherwise, they choose one with probability based on how much they trust it.
            let p_rand_media = (person.medias.len() as f64) / 2.; // TODO denom should be a config val
            if rng.gen::<f64>() < p_rand_media {
                pop.random("media")
            } else {
                let id = person.rand_edge(&mut rng, &person.medias);
                pop.get_agent(id).unwrap()
            }
        } else {
            // choose a person to talk to.
            let p_rand_person = (person.friends.len() as f64) / 2.; // TODO denom should be a config val
            if rng.gen::<f64>() < p_rand_person {
                pop.random("person") // TODO prob shouldnt be themselves
            } else {
                let id = person.rand_edge(&mut rng, &person.friends);
                pop.get_agent(id).unwrap()
            }
        };
        match other.state {
            State::Person(ref p) => {
                let op_idx = person.rand_opinion_idx(&mut rng, &p.opinions);
                let ref op1 = person.opinions[op_idx];
                let ref op2 = p.opinions[op_idx];

                // naively bootstrap trust for new person as 0
                let trust = match person.friends.get(&other.id) {
                    Some(t) => *t,
                    None => 0,
                };
                let p_opinion_shift = ((trust as f64) + 0.01) / 100.;
                if rng.gen::<f64>() < p_opinion_shift {
                    updates.queue(id, Update::Person(person.be_influenced(op_idx, op1, op2)));
                }

                updates.queue(id,
                              Update::Person(PersonUpdate::TrustShift {
                                  id: other.id,
                                  shift: person.trust_shift(op1, op2),
                                  edge_type: EdgeType::Friend,
                              }))
            }
            State::Media(ref m) => {
                let op_idx = person.rand_opinion_idx(&mut rng, &m.opinions);
                let ref op1 = person.opinions[op_idx];
                let ref op2 = m.opinions[op_idx];

                // "click" on the story
                updates.queue(other.id,
                              Update::Media(MediaUpdate::Click {
                                  idx: op_idx,
                                  polarity: op1.polarity,
                              }));

                // naively bootstrap trust for new media as 0
                let trust = match person.medias.get(&other.id) {
                    Some(t) => *t,
                    None => 0,
                };

                let p_opinion_shift = ((trust as f64) + 0.01) / 100.;
                if rng.gen::<f64>() < p_opinion_shift {
                    updates.queue(id, Update::Person(person.be_influenced(op_idx, op1, op2)));
                }

                updates.queue(id,
                              Update::Person(PersonUpdate::TrustShift {
                                  id: other.id,
                                  shift: person.trust_shift(op1, op2),
                                  edge_type: EdgeType::Media,
                              }))
            }
        }
    }

    fn update_person(&self, mut person: &mut Person, updates: Vec<Update>) -> bool {
        let mut updated = false;
        for update in updates {
            match update {
                Update::Person(u) => {
                    match u {
                        PersonUpdate::OpinionShift { idx, polarity } => {
                            let ref mut op = person.opinions[idx];
                            op.polarity += polarity;
                            updated = true;
                        }
                        PersonUpdate::TrustShift { id, shift, edge_type } => {
                            match edge_type {
                                EdgeType::Friend => {
                                    let trust = *person.friends.entry(id).or_insert(0) as i32;
                                    person.friends.insert(id, (trust + shift) as u32);
                                }
                                EdgeType::Media => {
                                    let trust = *person.medias.entry(id).or_insert(0) as i32;
                                    person.medias.insert(id, (trust + shift) as u32);
                                }
                            }
                            updated = true;
                        }
                    }
                }
                _ => (),
            }
        }
        updated
    }

    fn update_media(&self, mut media: &mut Media, updates: Vec<Update>) -> bool {
        let mut updated = false;
        for update in updates {
            match update {
                Update::Media(u) => {
                    match u {
                        MediaUpdate::Click { idx, polarity } => {
                            let ref mut op = media.opinions[idx];
                            let diff = polarity - op.polarity;
                            op.polarity += ((polarity as f64) * OPINION_SHIFT_PROPORTION)
                                .round() as i32;
                            op.priority += 1;
                            updated = true;
                        }
                    }
                }
                _ => (),
            }
        }
        updated
    }
}

impl Simulation for OpinionDynamicsSim {
    type State = State;
    type Update = Update;
    type World = World;

    fn decide<R: Redis>(&self,
                        agent: &Agent<Self::State>,
                        world: &Self::World,
                        pop: &Population<Self, R>,
                        updates: &mut Updates<Self>)
                        -> () {
        match agent.state {
            State::Person(ref p) => {
                self.decide_person(agent.id, p, pop, updates);
            }
            State::Media(ref m) => {
                self.decide_media(agent.id, m, pop, updates);
            }
        }
    }

    fn update(&self, mut state: &mut Self::State, updates: Vec<Self::Update>) -> bool {
        match *state {
            State::Person(ref mut p) => self.update_person(p, updates),
            State::Media(ref mut m) => self.update_media(m, updates),
        }
    }

    fn on_spawns<R: Redis>(&self,
                           agents: Vec<Agent<Self::State>>,
                           population: &Population<Self, R>)
                           -> () {
        // index newly created media/people
        // so we can sample them later
        let mut media = Vec::new();
        let mut people = Vec::new();
        let _: Vec<()> = agents.iter()
            .map(|a| {
                match a.state {
                    State::Person(_) => people.push(a.id),
                    State::Media(_) => media.push(a.id),
                }
            })
            .collect();
        let _: () = population.indexes("media", media);
        let _: () = population.indexes("people", people);
    }
}

// TODO
// person step:
//  - sample a person to talk to, based on trust. or encounter random.
//  - read a story from media, based on opinion alignment and importance
//  - talk to person
//  - change opinion based on media
//  - change opinion based on talking to person
//

fn main() {
    // TODO
    // - bootstrap social network
    // - people more likely talk to likeminded friends
    // - randomly meet new people

    let sim = OpinionDynamicsSim {};

    // TODO
    let world = World { weather: "sunny".to_string() };

    // Setup the manager
    let addr = "redis://127.0.0.1/";
    let pop_client = Client::open(addr).unwrap();
    let mut manager = Manager::new(addr, pop_client, sim.clone());

    let mut medias = vec![Media {
                              opinions: vec![Opinion {
                                                 polarity: -1,
                                                 priority: 5,
                                             },
                                             Opinion {
                                                 polarity: -5,
                                                 priority: 1,
                                             }],
                          },
                          Media {
                              opinions: vec![Opinion {
                                                 polarity: 1,
                                                 priority: 8,
                                             },
                                             Opinion {
                                                 polarity: 2,
                                                 priority: 4,
                                             }],
                          }];
    let media_ids = manager.spawns(medias.drain(..).map(|m| State::Media(m)).collect());

    let mut people = vec![Person::new(vec![Opinion {
                                               polarity: 1,
                                               priority: 1,
                                           },
                                           Opinion {
                                               polarity: 0,
                                               priority: 0,
                                           }]),
                          Person::new(vec![Opinion {
                                               polarity: -1,
                                               priority: 1,
                                           },
                                           Opinion {
                                               polarity: 1,
                                               priority: 0,
                                           }])];
    let people_ids = manager.spawns(people.drain(..).map(|m| State::Person(m)).collect());

    // run(sim, world, manager, 4, 10);
}
<|endoftext|>


<commit_before>// Copyright 2018 Bobby Powers. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

#![feature(alloc_system)]

// extern crate alloc_system;

//extern crate libc;
//use libc::geteuid;

use std::cmp::{Eq, Ordering};
use std::fs::{self, File};
use std::io::{BufRead, BufReader, Error, ErrorKind, Read, Result};

struct Lines<'a> {
    reader: BufReader<File>,
    buffer: &'a mut Vec<u8>,
}

impl<'a> Lines<'a> {
    fn new(file: File, buffer: &'a mut Vec<u8>) -> Lines {
        Lines {
            reader: BufReader::new(file),
            buffer: buffer,
        }
    }
}

impl<'a> Iterator for Lines<'a> {
    type Item = &'a [u8];

    fn next<'b>(&'b mut self) -> Option<Self::Item> {
        const NEWLINE: u8 = b'\n';

        self.buffer.truncate(0);

        match self.reader.read_until(NEWLINE, &mut self.buffer) {
            Ok(len) => {
                if len > 0 {
                    let by: &'b [u8] = self.buffer.as_slice();
                    Some(by)
                } else {
                    None
                }
            }
            Err(_) => None,
        }
    }
}

struct CmdStat {
    name: String,
    pid: i32,
    pss: f32,
    shared: f32,
    swap: f32,
    count: i32, // used when summing up
}

impl CmdStat {
    fn try_new(pid: i32) -> Result<Self> {
        let name = proc_name(pid)?;

        let mut stats = CmdStat {
            name,
            pid,
            pss: 0.0,
            shared: 0.0,
            swap: 0.0,
            count: 1,
        };

        // try to use rollups first, fall back to non-rollups otherwise
        if stats.collect_memory_usage(false).is_err() {
            stats.collect_memory_usage(false)?;
        }

        Ok(stats)
    }

    fn collect_memory_usage(&mut self, use_rollup: bool) -> Result<()> {
        const TY_PSS: &[u8] = b"Pss:";
        const TY_SWAP: &[u8] = b"Swap:";
        const TY_PRIVATE_CLEAN: &[u8] = b"Private_Clean:";
        const TY_PRIVATE_DIRTY: &[u8] = b"Private_Dirty:";

        let rollup_suffix = if use_rollup { "_rollup" } else { "" };
        let path = format!("/proc/{}/smaps{}", self.pid, rollup_suffix);
        let file = File::open(path)?;

        let pss_adjust = if use_rollup { 0.5 } else { 0.0 };

        let mut private: f32 = 0.0;
        let mut buffer = vec![];

        for line in Lines::new(file, &mut buffer) {
            if line.starts_with(TY_PSS) {
                if let Ok(n) = parse_line(&line) {
                    self.pss += n + pss_adjust;
                }
            } else if line.starts_with(TY_SWAP) {
                if let Ok(n) = parse_line(&line) {
                    self.swap += n;
                }
            } else if line.starts_with(TY_PRIVATE_CLEAN) || line.starts_with(TY_PRIVATE_DIRTY) {
                if let Ok(n) = parse_line(&line) {
                    private += n;
                }
            }
        }

        self.shared = self.pss - private;

        Ok(())
    }
}

fn parse_line(line: &[u8]) -> Result<f32> {
    use std::str;
    let kbs = unsafe { str::from_utf8_unchecked(&line[16..24]) };
    let num: f32 = kbs
        .trim_start()
        .parse()
        .map_err(|_e| Error::new(ErrorKind::Other, "parse error"))?;
    Ok(num)
}

impl PartialEq for CmdStat {
    fn eq(&self, other: &CmdStat) -> bool {
        self.name == other.name
    }
}
impl Eq for CmdStat {}

impl PartialOrd for CmdStat {
    fn partial_cmp(&self, other: &CmdStat) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

impl Ord for CmdStat {
    fn cmp(&self, other: &CmdStat) -> Ordering {
        self.name.cmp(&other.name)
    }
}

fn is_digit(d: char) -> bool {
    d >= '0' && d <= '9'
}

fn first_char(name: &std::ffi::OsStr) -> char {
    use std::os::unix::ffi::OsStrExt;
    name.as_bytes()[0] as char
}

fn is_dir(md: fs::Metadata) -> Result<fs::Metadata> {
    if md.is_dir() {
        Ok(md)
    } else {
        Err(Error::new(ErrorKind::NotFound, "not a directory"))
    }
}

#[inline(never)]
fn get_pids() -> Result<Vec<i32>> {
    let dir = fs::read_dir("/proc")?;

    // TODO: look at filter_map
    let pids: Vec<i32> = dir
        .filter(|e| e.is_ok()) // ignore bad dirent results
        .map(|e| e.unwrap()) // make this a list of dirents
        .filter(|e| is_digit(first_char(&e.file_name())))
        .filter(|e| fs::metadata(e.path()).and_then(is_dir).is_ok())
        .map(|e| i32::from_str_radix(e.file_name().to_str().unwrap(), 10).unwrap())
        .collect();

    Ok(pids)
}

fn proc_cmdline(pid: i32) -> Result<String> {
    let mut buf = String::new();
    let path = format!("/proc/{}/cmdline", pid);

    // TODO: we don't really care about reading all of the
    // cmdline, just the first 1024 chars is enough.
    let mut f = File::open(path)?;

    f.read_to_string(&mut buf)?;

    Ok(buf)
}

fn proc_name(pid: i32) -> Result<String> {
    let path = format!("/proc/{}/exe", pid);

    let owned_path = fs::read_link(path)?;
    let full_path = owned_path.to_string_lossy();
    let cmdline = proc_cmdline(pid)?;

    let result = if cmdline.starts_with(&*full_path) {
        // the basename (last component) of the path
        owned_path
            .file_name()
            .unwrap()
            .to_string_lossy()
            .to_string()
    } else {
        cmdline
    };

    Ok(result)
}

fn cmdstats_for(pids: Vec<i32>) -> Vec<CmdStat> {
    pids.into_iter()
        .filter_map(|pid| CmdStat::try_new(pid).ok())
        .collect()
}

fn main() {
    // TODO: command line flag parsing

    let pids_r = get_pids();
    if let Err(err) = pids_r {
        println!("get_pids: {}", err);
        return;
    }

    let pids = pids_r.unwrap();

    let mut stats = cmdstats_for(pids);

    stats.sort_unstable();

    stats.dedup_by(|a, b| {
        if a.name != b.name {
            return false;
        }
        b.pss += a.pss;
        b.shared += a.shared;
        b.swap += a.swap;
        b.count += 1;
        true
    });

    stats.sort_unstable_by(|a, b| a.pss.partial_cmp(&b.pss).unwrap_or(Ordering::Equal));

    for cmd in &stats {
        let swap = if cmd.swap > 0.0 {
            format!("{:10.1}", cmd.swap)
        } else {
            "".to_string()
        };
        println!(
            "{:10.1}{:10.1}{:10}\t{} ({})",
            cmd.pss / 1024.0,
            cmd.shared / 1024.0,
            swap,
            cmd.name,
            cmd.count
        )
    }

    let total_pss = stats.iter().fold(0.0, |sum, stat| sum + stat.pss);
    let total_swap = stats.iter().fold(0.0, |sum, stat| sum + stat.swap);

    println!(
        "#{:9.1}{:20.1}\tTOTAL USED BY PROCESSES",
        total_pss / 1024.0,
        total_swap / 1024.0
    );
}
<commit_msg>Revert "attempt at reducing allocations"<commit_after>// Copyright 2018 Bobby Powers. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

#![feature(alloc_system)]

extern crate alloc_system;

//extern crate libc;
//use libc::geteuid;

use std::cmp::{Eq, Ordering};
use std::fs::{self, File};
use std::io::{BufRead, BufReader, Error, ErrorKind, Read, Result};

struct Lines {
    reader: BufReader<File>,
}

impl Lines {
    fn new(file: File) -> Lines {
        Lines {
            reader: BufReader::new(file),
        }
    }
}

impl Iterator for Lines {
    type Item = Vec<u8>;

    fn next(&mut self) -> Option<Self::Item> {
        let mut buffer = vec![];

        const NEWLINE: u8 = b'\n';

        match self.reader.read_until(NEWLINE, &mut buffer) {
            Ok(len) => {
                if len > 0 {
                    Some(buffer)
                } else {
                    None
                }
            }
            Err(_) => None,
        }
    }
}

struct CmdStat {
    name: String,
    pid: i32,
    pss: f32,
    shared: f32,
    swap: f32,
    count: i32, // used when summing up
}

impl CmdStat {
    fn try_new(pid: i32) -> Result<Self> {
        let name = proc_name(pid)?;

        let mut stats = CmdStat {
            name,
            pid,
            pss: 0.0,
            shared: 0.0,
            swap: 0.0,
            count: 1,
        };

        // try to use rollups first, fall back to non-rollups otherwise
        if stats.collect_memory_usage(false).is_err() {
            stats.collect_memory_usage(false)?;
        }

        Ok(stats)
    }

    fn collect_memory_usage(&mut self, use_rollup: bool) -> Result<()> {
        const TY_PSS: &[u8] = b"Pss:";
        const TY_SWAP: &[u8] = b"Swap:";
        const TY_PRIVATE_CLEAN: &[u8] = b"Private_Clean:";
        const TY_PRIVATE_DIRTY: &[u8] = b"Private_Dirty:";

        let rollup_suffix = if use_rollup { "_rollup" } else { "" };
        let path = format!("/proc/{}/smaps{}", self.pid, rollup_suffix);
        let file = File::open(path)?;

        let pss_adjust = if use_rollup { 0.5 } else { 0.0 };

        let mut private: f32 = 0.0;

        for line in Lines::new(file) {
            if line.starts_with(TY_PSS) {
                if let Ok(n) = parse_line(&line) {
                    self.pss += n + pss_adjust;
                }
            } else if line.starts_with(TY_SWAP) {
                if let Ok(n) = parse_line(&line) {
                    self.swap += n;
                }
            } else if line.starts_with(TY_PRIVATE_CLEAN) || line.starts_with(TY_PRIVATE_DIRTY) {
                if let Ok(n) = parse_line(&line) {
                    private += n;
                }
            }
        }

        self.shared = self.pss - private;

        Ok(())
    }
}

fn parse_line(line: &[u8]) -> Result<f32> {
    use std::str;
    let kbs = unsafe { str::from_utf8_unchecked(&line[16..24]) };
    let num: f32 = kbs
        .trim_start()
        .parse()
        .map_err(|_e| Error::new(ErrorKind::Other, "parse error"))?;
    Ok(num)
}

impl PartialEq for CmdStat {
    fn eq(&self, other: &CmdStat) -> bool {
        self.name == other.name
    }
}
impl Eq for CmdStat {}

impl PartialOrd for CmdStat {
    fn partial_cmp(&self, other: &CmdStat) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

impl Ord for CmdStat {
    fn cmp(&self, other: &CmdStat) -> Ordering {
        self.name.cmp(&other.name)
    }
}

fn is_digit(d: char) -> bool {
    d >= '0' && d <= '9'
}

fn first_char(name: &std::ffi::OsStr) -> char {
    use std::os::unix::ffi::OsStrExt;
    name.as_bytes()[0] as char
}

fn is_dir(md: fs::Metadata) -> Result<fs::Metadata> {
    if md.is_dir() {
        Ok(md)
    } else {
        Err(Error::new(ErrorKind::NotFound, "not a directory"))
    }
}

#[inline(never)]
fn get_pids() -> Result<Vec<i32>> {
    let dir = fs::read_dir("/proc")?;

    // TODO: look at filter_map
    let pids: Vec<i32> = dir
        .filter(|e| e.is_ok()) // ignore bad dirent results
        .map(|e| e.unwrap()) // make this a list of dirents
        .filter(|e| is_digit(first_char(&e.file_name())))
        .filter(|e| fs::metadata(e.path()).and_then(is_dir).is_ok())
        .map(|e| i32::from_str_radix(e.file_name().to_str().unwrap(), 10).unwrap())
        .collect();

    Ok(pids)
}

fn proc_cmdline(pid: i32) -> Result<String> {
    let mut buf = String::new();
    let path = format!("/proc/{}/cmdline", pid);

    // TODO: we don't really care about reading all of the
    // cmdline, just the first 1024 chars is enough.
    let mut f = File::open(path)?;

    f.read_to_string(&mut buf)?;

    Ok(buf)
}

fn proc_name(pid: i32) -> Result<String> {
    let path = format!("/proc/{}/exe", pid);

    let owned_path = fs::read_link(path)?;
    let full_path = owned_path.to_string_lossy();
    let cmdline = proc_cmdline(pid)?;

    let result = if cmdline.starts_with(&*full_path) {
        // the basename (last component) of the path
        owned_path
            .file_name()
            .unwrap()
            .to_string_lossy()
            .to_string()
    } else {
        cmdline
    };

    Ok(result)
}

fn cmdstats_for(pids: Vec<i32>) -> Vec<CmdStat> {
    pids.into_iter()
        .filter_map(|pid| CmdStat::try_new(pid).ok())
        .collect()
}

fn main() {
    // TODO: command line flag parsing

    let pids_r = get_pids();
    if let Err(err) = pids_r {
        println!("get_pids: {}", err);
        return;
    }

    let pids = pids_r.unwrap();

    let mut stats = cmdstats_for(pids);

    stats.sort_unstable();

    stats.dedup_by(|a, b| {
        if a.name != b.name {
            return false;
        }
        b.pss += a.pss;
        b.shared += a.shared;
        b.swap += a.swap;
        b.count += 1;
        true
    });

    stats.sort_unstable_by(|a, b| a.pss.partial_cmp(&b.pss).unwrap_or(Ordering::Equal));

    for cmd in &stats {
        let swap = if cmd.swap > 0.0 {
            format!("{:10.1}", cmd.swap)
        } else {
            "".to_string()
        };
        println!(
            "{:10.1}{:10.1}{:10}\t{} ({})",
            cmd.pss / 1024.0,
            cmd.shared / 1024.0,
            swap,
            cmd.name,
            cmd.count
        )
    }

    let total_pss = stats.iter().fold(0.0, |sum, stat| sum + stat.pss);
    let total_swap = stats.iter().fold(0.0, |sum, stat| sum + stat.swap);

    println!(
        "#{:9.1}{:20.1}\tTOTAL USED BY PROCESSES",
        total_pss / 1024.0,
        total_swap / 1024.0
    );
}
<|endoftext|>


<commit_before>use super::common::*;
use crate::prelude::*;

use crate::idents;
use crate::model;
use crate::utils;
use std::iter::FromIterator;

use syn::spanned::Spanned;

/// Expands the `com_library` macro.
///
/// The macro expansion results in the following items:
///
/// - `DllGetClassObject` extern function implementation.
/// - `IntercomListClassObjects` extern function implementation.
pub fn expand_com_module(
    arg_tokens: TokenStreamNightly,
    com_library: bool,
) -> Result<TokenStreamNightly, model::ParseError>
{
    let mut output = vec![];
    let lib = model::ComLibrary::parse(&lib_name(), arg_tokens.into())?;

    // Create the match-statmeent patterns for each supposedly visible COM class.
    let mut match_arms = vec![];
    for struct_path in &lib.coclasses {
        // Construct the match pattern.
        let clsid_path = idents::clsid_path(struct_path);
        match_arms.push(quote_spanned!(struct_path.span() =>
            #clsid_path =>
                return Some(intercom::ClassFactory::<#struct_path>::create(riid, pout))
        ));
    }

    let try_submodule_class_factory = lib.submodules.iter().map(|submod| {
        quote!(
            if let Some(hr) = #submod::__get_module_class_factory(rclsid, riid, pout) {
                return Some(hr);
            }
        )
    });

    // Implement the __get_module_class_factory function.
    output.push(quote!(
        #[allow(dead_code)]
        #[doc(hidden)]
        pub unsafe fn __get_module_class_factory(
            rclsid : intercom::REFCLSID,
            riid : intercom::REFIID,
            pout : *mut intercom::raw::RawComPtr
        ) -> Option<intercom::raw::HRESULT>
        {
            // Create new class factory.
            // Specify a create function that is able to create all the
            // contained coclasses.
            match *rclsid {
                #( #match_arms, )*
                _ => {},
            };

            // Try each sub-module
            #( #try_submodule_class_factory )*

            None
        }
    ));

    // Implement DllGetClassObject and DllMain.
    //
    // This is more or less the only symbolic entry point that the COM
    // infrastructure uses. The COM client uses this method to acquire
    // the IClassFactory interfaces that are then used to construct the
    // actual coclasses.
    if com_library {
        let dll_get_class_object = get_dll_get_class_object_function();
        output.push(dll_get_class_object);
        output.push(quote!(
            #[doc(hidden)]
            static mut __INTERCOM_DLL_INSTANCE: *mut std::os::raw::c_void = 0 as _;

            #[cfg(windows)]
            #[no_mangle]
            #[allow(non_camel_case_types)]
            #[deprecated]
            #[doc(hidden)]
            pub extern "stdcall" fn DllMain(
                dll_instance: *mut std::os::raw::c_void,
                reason: u32,
                _reserved: *mut std::os::raw::c_void,
            ) -> bool
            {
                match reason {
                    // DLL_PROCESS_ATTACH
                    1 => unsafe {
                        __INTERCOM_DLL_INSTANCE = dll_instance;
                    },
                    _ => {}
                }
                true
            }
        ));
    }

    // Implement get_intercom_typelib()
    output.push(create_gather_module_types(&lib).map_err(model::ParseError::ComLibrary)?);
    if com_library {
        output.push(create_get_typelib_function(&lib).map_err(model::ParseError::ComLibrary)?);
    }

    // Implement the global DLL entry points
    if com_library {
        // DllListClassObjects returns all CLSIDs implemented in the crate.
        let list_class_objects = get_intercom_list_class_objects_function();
        output.push(list_class_objects);

        // DllListClassObjects returns all CLSIDs implemented in the crate.
        let dll_register_server = get_register_server_function(&lib);
        output.push(dll_register_server);
    }

    Ok(TokenStream::from_iter(output.into_iter()).into())
}

fn get_dll_get_class_object_function() -> TokenStream
{
    quote!(
        #[no_mangle]
        #[allow(non_snake_case)]
        #[allow(dead_code)]
        #[doc(hidden)]
        pub unsafe extern "system" fn DllGetClassObject(
            rclsid: intercom::REFCLSID,
            riid: intercom::REFIID,
            pout: *mut intercom::raw::RawComPtr,
        ) -> intercom::raw::HRESULT
        {
            // Delegate to the module implementation.
            if let Some(hr) = __get_module_class_factory(rclsid, riid, pout) {
                return hr;
            }

            // Try intercom built in types.
            if let Some(hr) = intercom::__get_module_class_factory(rclsid, riid, pout) {
                return hr;
            }

            intercom::raw::E_CLASSNOTAVAILABLE
        }
    )
}

fn create_gather_module_types(lib: &model::ComLibrary) -> Result<TokenStream, String>
{
    let create_class_typeinfo = lib.coclasses.iter().map(|path| {
        quote!(
            <#path as intercom::attributes::ComClassTypeInfo>::gather_type_info()
        )
    });
    let gather_submodule_types = lib
        .submodules
        .iter()
        .map(|path| quote!( #path::__gather_module_types()));
    Ok(quote!(
        pub fn __gather_module_types() -> Vec<intercom::typelib::TypeInfo>
        {
            vec![
                #( #create_class_typeinfo, )*
                #( #gather_submodule_types, )*
            ]
            .into_iter()
            .flatten()
            .collect()
        }
    ))
}

fn create_get_typelib_function(lib: &model::ComLibrary) -> Result<TokenStream, String>
{
    let lib_name = lib_name();
    let libid = utils::get_guid_tokens(&lib.libid, Span::call_site());
    Ok(quote!(
        #[no_mangle]
        pub unsafe extern "system" fn IntercomTypeLib(
            type_system: intercom::type_system::TypeSystemName,
            out: *mut intercom::raw::RawComPtr,
        ) -> intercom::raw::HRESULT
        {
            let mut tlib = intercom::ComBox::new(intercom::typelib::TypeLib::__new(
                    #lib_name.into(),
                    #libid,
                    "0.1".into(),
                    intercom::__gather_module_types()
                        .into_iter().chain(__gather_module_types())
                        .collect()
            ));
            let rc = intercom::ComRc::<intercom::typelib::IIntercomTypeLib>::from( &tlib );
            let itf = intercom::ComRc::detach(rc);
            *out = type_system.get_ptr(&itf);

            intercom::raw::S_OK
        }
    ))
}

fn get_intercom_list_class_objects_function() -> TokenStream
{
    quote!(
        #[no_mangle]
        #[allow(non_snake_case)]
        #[allow(dead_code)]
        #[doc(hidden)]
        pub unsafe extern "system" fn IntercomListClassObjects(
            pcount: *mut usize,
            pclsids: *mut *const intercom::CLSID,
        ) -> intercom::raw::HRESULT
        {
            // Do not crash due to invalid parameters.
            if pcount.is_null() {
                return intercom::raw::E_POINTER;
            }
            if pclsids.is_null() {
                return intercom::raw::E_POINTER;
            }

            // Store the available CLSID in a static variable so that we can
            // pass them as-is to the caller.
            static mut AVAILABLE_CLASSES: Option<Vec<intercom::CLSID>> = None;
            static INIT_AVAILABLE_CLASSES: std::sync::Once = std::sync::Once::new();
            INIT_AVAILABLE_CLASSES.call_once(|| unsafe {
                AVAILABLE_CLASSES = Some(
                    __gather_module_types()
                        .into_iter()
                        .chain(intercom::__gather_module_types())
                        .filter_map(|ty| match ty {
                            intercom::typelib::TypeInfo::Class(cls) => Some(cls.clsid.clone()),
                            _ => None,
                        })
                        .collect(),
                );
            });

            // com_struct will drop here and decrement the referenc ecount.
            // This is okay, as the query_interface incremented it, leaving
            // it at two at this point.
            let available_classes = AVAILABLE_CLASSES
                .as_ref()
                .expect("AVAILABLE_CLASSES was not initialized");
            *pcount = available_classes.len();
            *pclsids = available_classes.as_ptr();

            intercom::raw::S_OK
        }
    )
}

fn get_register_server_function(lib: &model::ComLibrary) -> TokenStream
{
    let lib_name = lib_name();
    let libid = utils::get_guid_tokens(&lib.libid, Span::call_site());
    quote!(
        #[cfg(windows)]
        #[no_mangle]
        #[allow(non_snake_case)]
        #[allow(dead_code)]
        #[doc(hidden)]
        pub unsafe extern "system" fn DllRegisterServer() -> intercom::raw::HRESULT
        {
            let mut tlib = intercom::typelib::TypeLib::__new(
                    #lib_name.into(),
                    #libid,
                    "0.1".into(),
                    intercom::__gather_module_types()
                        .into_iter().chain(__gather_module_types())
                        .collect()
            );
            match intercom::registry::register(__INTERCOM_DLL_INSTANCE, tlib) {
                Ok(_) => intercom::raw::S_OK,
                Err(hr) => hr,
            }
        }

        #[cfg(windows)]
        #[no_mangle]
        #[allow(non_snake_case)]
        #[allow(dead_code)]
        #[doc(hidden)]
        pub unsafe extern "system" fn DllUnregisterServer() -> intercom::raw::HRESULT
        {
            let mut tlib = intercom::typelib::TypeLib::__new(
                    #lib_name.into(),
                    #libid,
                    "0.1".into(),
                    intercom::__gather_module_types()
                        .into_iter().chain(__gather_module_types())
                        .collect()
            );
            match intercom::registry::unregister(__INTERCOM_DLL_INSTANCE, tlib) {
                Ok(_) => intercom::raw::S_OK,
                Err(hr) => hr,
            }
        }
    )
}
<commit_msg>DLL_INSTANCE is only needed on Windows<commit_after>use super::common::*;
use crate::prelude::*;

use crate::idents;
use crate::model;
use crate::utils;
use std::iter::FromIterator;

use syn::spanned::Spanned;

/// Expands the `com_library` macro.
///
/// The macro expansion results in the following items:
///
/// - `DllGetClassObject` extern function implementation.
/// - `IntercomListClassObjects` extern function implementation.
pub fn expand_com_module(
    arg_tokens: TokenStreamNightly,
    com_library: bool,
) -> Result<TokenStreamNightly, model::ParseError>
{
    let mut output = vec![];
    let lib = model::ComLibrary::parse(&lib_name(), arg_tokens.into())?;

    // Create the match-statmeent patterns for each supposedly visible COM class.
    let mut match_arms = vec![];
    for struct_path in &lib.coclasses {
        // Construct the match pattern.
        let clsid_path = idents::clsid_path(struct_path);
        match_arms.push(quote_spanned!(struct_path.span() =>
            #clsid_path =>
                return Some(intercom::ClassFactory::<#struct_path>::create(riid, pout))
        ));
    }

    let try_submodule_class_factory = lib.submodules.iter().map(|submod| {
        quote!(
            if let Some(hr) = #submod::__get_module_class_factory(rclsid, riid, pout) {
                return Some(hr);
            }
        )
    });

    // Implement the __get_module_class_factory function.
    output.push(quote!(
        #[allow(dead_code)]
        #[doc(hidden)]
        pub unsafe fn __get_module_class_factory(
            rclsid : intercom::REFCLSID,
            riid : intercom::REFIID,
            pout : *mut intercom::raw::RawComPtr
        ) -> Option<intercom::raw::HRESULT>
        {
            // Create new class factory.
            // Specify a create function that is able to create all the
            // contained coclasses.
            match *rclsid {
                #( #match_arms, )*
                _ => {},
            };

            // Try each sub-module
            #( #try_submodule_class_factory )*

            None
        }
    ));

    // Implement DllGetClassObject and DllMain.
    //
    // This is more or less the only symbolic entry point that the COM
    // infrastructure uses. The COM client uses this method to acquire
    // the IClassFactory interfaces that are then used to construct the
    // actual coclasses.
    if com_library {
        let dll_get_class_object = get_dll_get_class_object_function();
        output.push(dll_get_class_object);
        output.push(quote!(
            #[cfg(windows)]
            #[doc(hidden)]
            static mut __INTERCOM_DLL_INSTANCE: *mut std::os::raw::c_void = 0 as _;

            #[cfg(windows)]
            #[no_mangle]
            #[allow(non_camel_case_types)]
            #[deprecated]
            #[doc(hidden)]
            pub extern "stdcall" fn DllMain(
                dll_instance: *mut std::os::raw::c_void,
                reason: u32,
                _reserved: *mut std::os::raw::c_void,
            ) -> bool
            {
                match reason {
                    // DLL_PROCESS_ATTACH
                    1 => unsafe {
                        __INTERCOM_DLL_INSTANCE = dll_instance;
                    },
                    _ => {}
                }
                true
            }
        ));
    }

    // Implement get_intercom_typelib()
    output.push(create_gather_module_types(&lib).map_err(model::ParseError::ComLibrary)?);
    if com_library {
        output.push(create_get_typelib_function(&lib).map_err(model::ParseError::ComLibrary)?);
    }

    // Implement the global DLL entry points
    if com_library {
        // DllListClassObjects returns all CLSIDs implemented in the crate.
        let list_class_objects = get_intercom_list_class_objects_function();
        output.push(list_class_objects);

        // DllListClassObjects returns all CLSIDs implemented in the crate.
        let dll_register_server = get_register_server_function(&lib);
        output.push(dll_register_server);
    }

    Ok(TokenStream::from_iter(output.into_iter()).into())
}

fn get_dll_get_class_object_function() -> TokenStream
{
    quote!(
        #[no_mangle]
        #[allow(non_snake_case)]
        #[allow(dead_code)]
        #[doc(hidden)]
        pub unsafe extern "system" fn DllGetClassObject(
            rclsid: intercom::REFCLSID,
            riid: intercom::REFIID,
            pout: *mut intercom::raw::RawComPtr,
        ) -> intercom::raw::HRESULT
        {
            // Delegate to the module implementation.
            if let Some(hr) = __get_module_class_factory(rclsid, riid, pout) {
                return hr;
            }

            // Try intercom built in types.
            if let Some(hr) = intercom::__get_module_class_factory(rclsid, riid, pout) {
                return hr;
            }

            intercom::raw::E_CLASSNOTAVAILABLE
        }
    )
}

fn create_gather_module_types(lib: &model::ComLibrary) -> Result<TokenStream, String>
{
    let create_class_typeinfo = lib.coclasses.iter().map(|path| {
        quote!(
            <#path as intercom::attributes::ComClassTypeInfo>::gather_type_info()
        )
    });
    let gather_submodule_types = lib
        .submodules
        .iter()
        .map(|path| quote!( #path::__gather_module_types()));
    Ok(quote!(
        pub fn __gather_module_types() -> Vec<intercom::typelib::TypeInfo>
        {
            vec![
                #( #create_class_typeinfo, )*
                #( #gather_submodule_types, )*
            ]
            .into_iter()
            .flatten()
            .collect()
        }
    ))
}

fn create_get_typelib_function(lib: &model::ComLibrary) -> Result<TokenStream, String>
{
    let lib_name = lib_name();
    let libid = utils::get_guid_tokens(&lib.libid, Span::call_site());
    Ok(quote!(
        #[no_mangle]
        pub unsafe extern "system" fn IntercomTypeLib(
            type_system: intercom::type_system::TypeSystemName,
            out: *mut intercom::raw::RawComPtr,
        ) -> intercom::raw::HRESULT
        {
            let mut tlib = intercom::ComBox::new(intercom::typelib::TypeLib::__new(
                    #lib_name.into(),
                    #libid,
                    "0.1".into(),
                    intercom::__gather_module_types()
                        .into_iter().chain(__gather_module_types())
                        .collect()
            ));
            let rc = intercom::ComRc::<intercom::typelib::IIntercomTypeLib>::from( &tlib );
            let itf = intercom::ComRc::detach(rc);
            *out = type_system.get_ptr(&itf);

            intercom::raw::S_OK
        }
    ))
}

fn get_intercom_list_class_objects_function() -> TokenStream
{
    quote!(
        #[no_mangle]
        #[allow(non_snake_case)]
        #[allow(dead_code)]
        #[doc(hidden)]
        pub unsafe extern "system" fn IntercomListClassObjects(
            pcount: *mut usize,
            pclsids: *mut *const intercom::CLSID,
        ) -> intercom::raw::HRESULT
        {
            // Do not crash due to invalid parameters.
            if pcount.is_null() {
                return intercom::raw::E_POINTER;
            }
            if pclsids.is_null() {
                return intercom::raw::E_POINTER;
            }

            // Store the available CLSID in a static variable so that we can
            // pass them as-is to the caller.
            static mut AVAILABLE_CLASSES: Option<Vec<intercom::CLSID>> = None;
            static INIT_AVAILABLE_CLASSES: std::sync::Once = std::sync::Once::new();
            INIT_AVAILABLE_CLASSES.call_once(|| unsafe {
                AVAILABLE_CLASSES = Some(
                    __gather_module_types()
                        .into_iter()
                        .chain(intercom::__gather_module_types())
                        .filter_map(|ty| match ty {
                            intercom::typelib::TypeInfo::Class(cls) => Some(cls.clsid.clone()),
                            _ => None,
                        })
                        .collect(),
                );
            });

            // com_struct will drop here and decrement the referenc ecount.
            // This is okay, as the query_interface incremented it, leaving
            // it at two at this point.
            let available_classes = AVAILABLE_CLASSES
                .as_ref()
                .expect("AVAILABLE_CLASSES was not initialized");
            *pcount = available_classes.len();
            *pclsids = available_classes.as_ptr();

            intercom::raw::S_OK
        }
    )
}

fn get_register_server_function(lib: &model::ComLibrary) -> TokenStream
{
    let lib_name = lib_name();
    let libid = utils::get_guid_tokens(&lib.libid, Span::call_site());
    quote!(
        #[cfg(windows)]
        #[no_mangle]
        #[allow(non_snake_case)]
        #[allow(dead_code)]
        #[doc(hidden)]
        pub unsafe extern "system" fn DllRegisterServer() -> intercom::raw::HRESULT
        {
            let mut tlib = intercom::typelib::TypeLib::__new(
                    #lib_name.into(),
                    #libid,
                    "0.1".into(),
                    intercom::__gather_module_types()
                        .into_iter().chain(__gather_module_types())
                        .collect()
            );
            match intercom::registry::register(__INTERCOM_DLL_INSTANCE, tlib) {
                Ok(_) => intercom::raw::S_OK,
                Err(hr) => hr,
            }
        }

        #[cfg(windows)]
        #[no_mangle]
        #[allow(non_snake_case)]
        #[allow(dead_code)]
        #[doc(hidden)]
        pub unsafe extern "system" fn DllUnregisterServer() -> intercom::raw::HRESULT
        {
            let mut tlib = intercom::typelib::TypeLib::__new(
                    #lib_name.into(),
                    #libid,
                    "0.1".into(),
                    intercom::__gather_module_types()
                        .into_iter().chain(__gather_module_types())
                        .collect()
            );
            match intercom::registry::unregister(__INTERCOM_DLL_INSTANCE, tlib) {
                Ok(_) => intercom::raw::S_OK,
                Err(hr) => hr,
            }
        }
    )
}
<|endoftext|>


<commit_before>/* 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/. */

<%namespace name="helpers" file="/helpers.mako.rs" />
<% from data import Method %>

// Non-standard properties that Gecko uses for XUL elements.
<% data.new_style_struct("XUL", inherited=False) %>

${helpers.single_keyword("-moz-box-align", "stretch start center baseline end",
                         products="gecko", gecko_ffi_name="mBoxAlign",
                         gecko_enum_prefix="StyleBoxAlign",
                         gecko_inexhaustive=True,
                         animation_value_type="discrete",
                         alias="-webkit-box-align",
                         spec="Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/box-align)")}

${helpers.single_keyword("-moz-box-direction", "normal reverse",
                         products="gecko", gecko_ffi_name="mBoxDirection",
                         gecko_enum_prefix="StyleBoxDirection",
                         animation_value_type="none",
                         alias="-webkit-box-direction",
                         spec="Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/box-direction)")}

${helpers.predefined_type("-moz-box-flex", "Number", "0.0", "parse_non_negative",
                          products="gecko", gecko_ffi_name="mBoxFlex",
                          animation_value_type="none",
                          alias="-webkit-box-flex",
                          spec="Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/box-flex)")}

${helpers.single_keyword("-moz-box-orient", "horizontal vertical",
                         products="gecko", gecko_ffi_name="mBoxOrient",
                         extra_gecko_aliases="inline-axis=horizontal block-axis=vertical",
                         gecko_enum_prefix="StyleBoxOrient",
                         animation_value_type="none",
                         alias="-webkit-box-orient",
                         spec="Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/box-orient)")}

${helpers.single_keyword("-moz-box-pack", "start center end justify",
                         products="gecko", gecko_ffi_name="mBoxPack",
                         gecko_enum_prefix="StyleBoxPack",
                         animation_value_type="none",
                         alias="-webkit-box-pack",
                         spec="Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/box-pack)")}

${helpers.single_keyword("-moz-stack-sizing", "stretch-to-fit ignore ignore-horizontal ignore-vertical",
                         products="gecko", gecko_ffi_name="mStackSizing",
                         gecko_enum_prefix="StyleStackSizing",
                         animation_value_type="none",
                         spec="Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/-moz-stack-sizing)")}

${helpers.predefined_type("-moz-box-ordinal-group", "Integer", "0",
                          parse_method="parse_non_negative",
                          products="gecko",
                          alias="-webkit-box-ordinal-group",
                          gecko_ffi_name="mBoxOrdinal",
                          animation_value_type="none",
                          spec="Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/-moz-box-ordinal-group)")}
<commit_msg>Implements xul related discrete animatable properties<commit_after>/* 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/. */

<%namespace name="helpers" file="/helpers.mako.rs" />
<% from data import Method %>

// Non-standard properties that Gecko uses for XUL elements.
<% data.new_style_struct("XUL", inherited=False) %>

${helpers.single_keyword("-moz-box-align", "stretch start center baseline end",
                         products="gecko", gecko_ffi_name="mBoxAlign",
                         gecko_enum_prefix="StyleBoxAlign",
                         gecko_inexhaustive=True,
                         animation_value_type="discrete",
                         alias="-webkit-box-align",
                         spec="Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/box-align)")}

${helpers.single_keyword("-moz-box-direction", "normal reverse",
                         products="gecko", gecko_ffi_name="mBoxDirection",
                         gecko_enum_prefix="StyleBoxDirection",
                         gecko_inexhaustive=True,
                         animation_value_type="discrete",
                         alias="-webkit-box-direction",
                         spec="Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/box-direction)")}

${helpers.predefined_type("-moz-box-flex", "Number", "0.0", "parse_non_negative",
                          products="gecko", gecko_ffi_name="mBoxFlex",
                          animation_value_type="none",
                          alias="-webkit-box-flex",
                          spec="Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/box-flex)")}

${helpers.single_keyword("-moz-box-orient", "horizontal vertical",
                         products="gecko", gecko_ffi_name="mBoxOrient",
                         extra_gecko_aliases="inline-axis=horizontal block-axis=vertical",
                         gecko_enum_prefix="StyleBoxOrient",
                         gecko_inexhaustive=True,
                         animation_value_type="discrete",
                         alias="-webkit-box-orient",
                         spec="Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/box-orient)")}

${helpers.single_keyword("-moz-box-pack", "start center end justify",
                         products="gecko", gecko_ffi_name="mBoxPack",
                         gecko_enum_prefix="StyleBoxPack",
                         gecko_inexhaustive=True,
                         animation_value_type="discrete",
                         alias="-webkit-box-pack",
                         spec="Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/box-pack)")}

${helpers.single_keyword("-moz-stack-sizing", "stretch-to-fit ignore ignore-horizontal ignore-vertical",
                         products="gecko", gecko_ffi_name="mStackSizing",
                         gecko_enum_prefix="StyleStackSizing",
                         gecko_inexhaustive=True,
                         animation_value_type="discrete",
                         spec="Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/-moz-stack-sizing)")}

${helpers.predefined_type("-moz-box-ordinal-group", "Integer", "0",
                          parse_method="parse_non_negative",
                          products="gecko",
                          alias="-webkit-box-ordinal-group",
                          gecko_ffi_name="mBoxOrdinal",
                          animation_value_type="none",
                          spec="Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/-moz-box-ordinal-group)")}
<|endoftext|>


<commit_before>/* 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 std::fmt;

use text_writer::{mod, TextWriter};

use ast::*;
use ast::ComponentValue::*;

pub trait ToCss for Sized? {
    /// Serialize `self` in CSS syntax, writing to `dest`.
    fn to_css<W>(&self, dest: &mut W) -> text_writer::Result where W: TextWriter;

    /// Serialize `self` in CSS syntax and return a string.
    ///
    /// (This is a convenience wrapper for `to_css` and probably should not be overridden.)
    #[inline]
    fn to_css_string(&self) -> String {
        let mut s = String::new();
        self.to_css(&mut s).unwrap();
        s
    }

    /// Serialize `self` in CSS syntax and return a result compatible with `std::fmt::Show`.
    ///
    /// Typical usage is, for a `Foo` that implements `ToCss`:
    ///
    /// ```{rust,ignore}
    /// use std::fmt;
    /// impl fmt::Show for Foo {
    ///     #[inline] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.fmt_to_css(f) }
    /// }
    /// ```
    ///
    /// (This is a convenience wrapper for `to_css` and probably should not be overridden.)
    #[inline]
    fn fmt_to_css<W>(&self, dest: &mut W) -> fmt::Result where W: TextWriter {
        self.to_css(dest).map_err(|_| fmt::WriteError)
    }
}

impl ToCss for ComponentValue {
    fn to_css<W>(&self, dest: &mut W) -> text_writer::Result where W: TextWriter {
        match self {
            &Ident(ref value) => try!(serialize_identifier(value.as_slice(), dest)),
            &AtKeyword(ref value) => {
                try!(dest.write_char('@'));
                try!(serialize_identifier(value.as_slice(), dest));
            },
            &Hash(ref value) => {
                try!(dest.write_char('#'));
                for c in value.as_slice().chars() {
                    try!(serialize_char(c, dest, /* is_identifier_start = */ false));
                }
            },
            &IDHash(ref value) => {
                try!(dest.write_char('#'));
                try!(serialize_identifier(value.as_slice(), dest));
            }
            &QuotedString(ref value) => try!(serialize_string(value.as_slice(), dest)),
            &URL(ref value) => {
                try!(dest.write_str("url("));
                try!(serialize_string(value.as_slice(), dest));
                try!(dest.write_char(')'));
            },
            &Delim(value) => try!(dest.write_char(value)),

            &Number(ref value) => try!(dest.write_str(value.representation.as_slice())),
            &Percentage(ref value) => {
                try!(dest.write_str(value.representation.as_slice()));
                try!(dest.write_char('%'));
            },
            &Dimension(ref value, ref unit) => {
                try!(dest.write_str(value.representation.as_slice()));
                // Disambiguate with scientific notation.
                let unit = unit.as_slice();
                if unit == "e" || unit == "E" || unit.starts_with("e-") || unit.starts_with("E-") {
                    try!(dest.write_str("\\65 "));
                    for c in unit.slice_from(1).chars() {
                        try!(serialize_char(c, dest, /* is_identifier_start = */ false));
                    }
                } else {
                    try!(serialize_identifier(unit, dest));
                }
            },

            &UnicodeRange(start, end) => {
                try!(dest.write_str(format!("U+{:X}", start).as_slice()));
                if end != start {
                    try!(dest.write_str(format!("-{:X}", end).as_slice()));
                }
            }

            &WhiteSpace => try!(dest.write_char(' ')),
            &Colon => try!(dest.write_char(':')),
            &Semicolon => try!(dest.write_char(';')),
            &Comma => try!(dest.write_char(',')),
            &IncludeMatch => try!(dest.write_str("~=")),
            &DashMatch => try!(dest.write_str("|=")),
            &PrefixMatch => try!(dest.write_str("^=")),
            &SuffixMatch => try!(dest.write_str("$=")),
            &SubstringMatch => try!(dest.write_str("*=")),
            &Column => try!(dest.write_str("||")),
            &CDO => try!(dest.write_str("<!--")),
            &CDC => try!(dest.write_str("-->")),

            &Function(ref name, ref arguments) => {
                try!(serialize_identifier(name.as_slice(), dest));
                try!(dest.write_char('('));
                try!(arguments.to_css(dest));
                try!(dest.write_char(')'));
            },
            &ParenthesisBlock(ref content) => {
                try!(dest.write_char('('));
                try!(content.to_css(dest));
                try!(dest.write_char(')'));
            },
            &SquareBracketBlock(ref content) => {
                try!(dest.write_char('['));
                try!(content.to_css(dest));
                try!(dest.write_char(']'));
            },
            &CurlyBracketBlock(ref content) => {
                try!(dest.write_char('{'));
                try!(content.to_css(dest));
                try!(dest.write_char('}'));
            },

            &BadURL => try!(dest.write_str("url(<bad url>)")),
            &BadString => try!(dest.write_str("\"<bad string>\n")),
            &CloseParenthesis => try!(dest.write_char(')')),
            &CloseSquareBracket => try!(dest.write_char(']')),
            &CloseCurlyBracket => try!(dest.write_char('}')),
        }
        Ok(())
    }
}

pub fn serialize_identifier<W>(value: &str, dest: &mut W) -> text_writer::Result
where W:TextWriter {
    // TODO: avoid decoding/re-encoding UTF-8?
    let mut iter = value.chars();
    let mut c = iter.next().unwrap();
    if c == '-' {
        c = match iter.next() {
            None => return dest.write_str("\\-"),
            Some(c) => { try!(dest.write_char('-')); c },
        }
    };
    try!(serialize_char(c, dest, /* is_identifier_start = */ true));
    for c in iter {
        try!(serialize_char(c, dest, /* is_identifier_start = */ false));
    }
    Ok(())
}

#[inline]
fn serialize_char<W>(c: char, dest: &mut W, is_identifier_start: bool) -> text_writer::Result
where W: TextWriter {
    match c {
        '0'...'9' if is_identifier_start => try!(dest.write_str(format!("\\3{} ", c).as_slice())),
        '-' if is_identifier_start => try!(dest.write_str("\\-")),
        '0'...'9' | 'A'...'Z' | 'a'...'z' | '_' | '-' => try!(dest.write_char(c)),
        _ if c > '\x7F' => try!(dest.write_char(c)),
        '\n' => try!(dest.write_str("\\A ")),
        '\r' => try!(dest.write_str("\\D ")),
        '\x0C' => try!(dest.write_str("\\C ")),
        _ => { try!(dest.write_char('\\')); try!(dest.write_char(c)) },
    };
    Ok(())
}

pub fn serialize_string<W>(value: &str, dest: &mut W) -> text_writer::Result
where W: TextWriter {
    try!(dest.write_char('"'));
    // TODO: avoid decoding/re-encoding UTF-8?
    for c in value.chars() {
        match c {
            '"' => try!(dest.write_str("\\\"")),
            '\\' => try!(dest.write_str("\\\\")),
            '\n' => try!(dest.write_str("\\A ")),
            '\r' => try!(dest.write_str("\\D ")),
            '\x0C' => try!(dest.write_str("\\C ")),
            _ => try!(dest.write_char(c)),
        };
    }
    dest.write_char('"')
}

impl<'a> ToCss for [ComponentValue] {
    fn to_css<W>(&self, dest: &mut W) -> text_writer::Result where W: TextWriter {
        component_values_to_css(self.iter(), dest)
    }
}

impl<'a> ToCss for [Node] {
    fn to_css<W>(&self, dest: &mut W) -> text_writer::Result where W: TextWriter {
        let component_values = self.iter().map(|n| match n { &(ref c, _) => c });
        component_values_to_css(component_values, dest)
    }
}

fn component_values_to_css<'a, I, W>(mut iter: I, dest: &mut W) -> text_writer::Result
where I: Iterator<&'a ComponentValue>, W: TextWriter {
    let mut previous = match iter.next() {
        None => return Ok(()),
        Some(first) => { try!(first.to_css(dest)); first }
    };
    macro_rules! matches(
        ($value:expr, $($pattern:pat)|+) => (
            match $value { $($pattern)|+ => true, _ => false }
        );
    )
    // This does not borrow-check: for component_value in iter {
    loop { match iter.next() { None => break, Some(component_value) => {
        let (a, b) = (previous, component_value);
        if (
            matches!(*a, Ident(..) | AtKeyword(..) | Hash(..) | IDHash(..) |
                         Dimension(..) | Delim('#') | Delim('-') | Number(..)) &&
            matches!(*b, Ident(..) | Function(..) | URL(..) | BadURL(..) |
                         Number(..) | Percentage(..) | Dimension(..) | UnicodeRange(..))
        ) || (
            matches!(*a, Ident(..)) &&
            matches!(*b, ParenthesisBlock(..))
        ) || (
            matches!(*a, Ident(..) | AtKeyword(..) | Hash(..) | IDHash(..) | Dimension(..)) &&
            matches!(*b, Delim('-') | CDC)
        ) || (
            matches!(*a, Delim('#') | Delim('-') | Number(..) | Delim('@')) &&
            matches!(*b, Ident(..) | Function(..) | URL(..) | BadURL(..))
        ) || (
            matches!(*a, Delim('@')) &&
            matches!(*b, Ident(..) | Function(..) | URL(..) | BadURL(..) |
                         UnicodeRange(..) | Delim('-'))
        ) || (
            matches!(*a, UnicodeRange(..) | Delim('.') | Delim('+')) &&
            matches!(*b, Number(..) | Percentage(..) | Dimension(..))
        ) || (
            matches!(*a, UnicodeRange(..)) &&
            matches!(*b, Ident(..) | Function(..) | Delim('?'))
        ) || (match (a, b) { (&Delim(a), &Delim(b)) => matches!((a, b),
            ('#', '-') |
            ('$', '=') |
            ('*', '=') |
            ('^', '=') |
            ('~', '=') |
            ('|', '=') |
            ('|', '|') |
            ('/', '*')
        ), _ => false }) {
            try!(dest.write_str("/**/"));
        }
        // Skip whitespace when '\n' was previously written at the previous iteration.
        if !matches!((previous, component_value), (&Delim('\\'), &WhiteSpace)) {
            try!(component_value.to_css(dest));
        }
        if component_value == &Delim('\\') {
            try!(dest.write_char('\n'));
        }
        previous = component_value;
    }}}
    Ok(())
}

impl ToCss for Declaration {
    fn to_css<W>(&self, dest: &mut W) -> text_writer::Result where W: TextWriter {
        try!(dest.write_str(self.name.as_slice()));
        try!(dest.write_char(':'));
        try!(self.value.to_css(dest));
        Ok(())
    }
}

impl ToCss for QualifiedRule {
    fn to_css<W>(&self, dest: &mut W) -> text_writer::Result where W: TextWriter {
        try!(self.prelude.to_css(dest));
        try!(dest.write_char('{'));
        try!(self.block.to_css(dest));
        try!(dest.write_char('}'));
        Ok(())
    }
}

impl ToCss for AtRule {
    fn to_css<W>(&self, dest: &mut W) -> text_writer::Result where W: TextWriter {
        try!(dest.write_char('@'));
        try!(dest.write_str(self.name.as_slice()));
        try!(self.prelude.to_css(dest));
        match self.block {
            Some(ref block) => {
                try!(dest.write_char('{'));
                try!(block.to_css(dest));
                try!(dest.write_char('}'));
            }
            None => try!(dest.write_char(';'))
        }
        Ok(())
    }
}

impl ToCss for DeclarationListItem {
    fn to_css<W>(&self, dest: &mut W) -> text_writer::Result where W: TextWriter {
        match *self {
            DeclarationListItem::Declaration(ref declaration) => declaration.to_css(dest),
            DeclarationListItem::AtRule(ref at_rule) => at_rule.to_css(dest),
        }
    }
}

impl ToCss for Rule {
    fn to_css<W>(&self, dest: &mut W) -> text_writer::Result where W: TextWriter {
        match *self {
            Rule::QualifiedRule(ref rule) => rule.to_css(dest),
            Rule::AtRule(ref rule) => rule.to_css(dest),
        }
    }
}
<commit_msg>Implement ToCss for declaration lists<commit_after>/* 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 std::fmt;

use text_writer::{mod, TextWriter};

use ast::*;
use ast::ComponentValue::*;

pub trait ToCss for Sized? {
    /// Serialize `self` in CSS syntax, writing to `dest`.
    fn to_css<W>(&self, dest: &mut W) -> text_writer::Result where W: TextWriter;

    /// Serialize `self` in CSS syntax and return a string.
    ///
    /// (This is a convenience wrapper for `to_css` and probably should not be overridden.)
    #[inline]
    fn to_css_string(&self) -> String {
        let mut s = String::new();
        self.to_css(&mut s).unwrap();
        s
    }

    /// Serialize `self` in CSS syntax and return a result compatible with `std::fmt::Show`.
    ///
    /// Typical usage is, for a `Foo` that implements `ToCss`:
    ///
    /// ```{rust,ignore}
    /// use std::fmt;
    /// impl fmt::Show for Foo {
    ///     #[inline] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.fmt_to_css(f) }
    /// }
    /// ```
    ///
    /// (This is a convenience wrapper for `to_css` and probably should not be overridden.)
    #[inline]
    fn fmt_to_css<W>(&self, dest: &mut W) -> fmt::Result where W: TextWriter {
        self.to_css(dest).map_err(|_| fmt::WriteError)
    }
}

impl ToCss for ComponentValue {
    fn to_css<W>(&self, dest: &mut W) -> text_writer::Result where W: TextWriter {
        match self {
            &Ident(ref value) => try!(serialize_identifier(value.as_slice(), dest)),
            &AtKeyword(ref value) => {
                try!(dest.write_char('@'));
                try!(serialize_identifier(value.as_slice(), dest));
            },
            &Hash(ref value) => {
                try!(dest.write_char('#'));
                for c in value.as_slice().chars() {
                    try!(serialize_char(c, dest, /* is_identifier_start = */ false));
                }
            },
            &IDHash(ref value) => {
                try!(dest.write_char('#'));
                try!(serialize_identifier(value.as_slice(), dest));
            }
            &QuotedString(ref value) => try!(serialize_string(value.as_slice(), dest)),
            &URL(ref value) => {
                try!(dest.write_str("url("));
                try!(serialize_string(value.as_slice(), dest));
                try!(dest.write_char(')'));
            },
            &Delim(value) => try!(dest.write_char(value)),

            &Number(ref value) => try!(dest.write_str(value.representation.as_slice())),
            &Percentage(ref value) => {
                try!(dest.write_str(value.representation.as_slice()));
                try!(dest.write_char('%'));
            },
            &Dimension(ref value, ref unit) => {
                try!(dest.write_str(value.representation.as_slice()));
                // Disambiguate with scientific notation.
                let unit = unit.as_slice();
                if unit == "e" || unit == "E" || unit.starts_with("e-") || unit.starts_with("E-") {
                    try!(dest.write_str("\\65 "));
                    for c in unit.slice_from(1).chars() {
                        try!(serialize_char(c, dest, /* is_identifier_start = */ false));
                    }
                } else {
                    try!(serialize_identifier(unit, dest));
                }
            },

            &UnicodeRange(start, end) => {
                try!(dest.write_str(format!("U+{:X}", start).as_slice()));
                if end != start {
                    try!(dest.write_str(format!("-{:X}", end).as_slice()));
                }
            }

            &WhiteSpace => try!(dest.write_char(' ')),
            &Colon => try!(dest.write_char(':')),
            &Semicolon => try!(dest.write_char(';')),
            &Comma => try!(dest.write_char(',')),
            &IncludeMatch => try!(dest.write_str("~=")),
            &DashMatch => try!(dest.write_str("|=")),
            &PrefixMatch => try!(dest.write_str("^=")),
            &SuffixMatch => try!(dest.write_str("$=")),
            &SubstringMatch => try!(dest.write_str("*=")),
            &Column => try!(dest.write_str("||")),
            &CDO => try!(dest.write_str("<!--")),
            &CDC => try!(dest.write_str("-->")),

            &Function(ref name, ref arguments) => {
                try!(serialize_identifier(name.as_slice(), dest));
                try!(dest.write_char('('));
                try!(arguments.to_css(dest));
                try!(dest.write_char(')'));
            },
            &ParenthesisBlock(ref content) => {
                try!(dest.write_char('('));
                try!(content.to_css(dest));
                try!(dest.write_char(')'));
            },
            &SquareBracketBlock(ref content) => {
                try!(dest.write_char('['));
                try!(content.to_css(dest));
                try!(dest.write_char(']'));
            },
            &CurlyBracketBlock(ref content) => {
                try!(dest.write_char('{'));
                try!(content.to_css(dest));
                try!(dest.write_char('}'));
            },

            &BadURL => try!(dest.write_str("url(<bad url>)")),
            &BadString => try!(dest.write_str("\"<bad string>\n")),
            &CloseParenthesis => try!(dest.write_char(')')),
            &CloseSquareBracket => try!(dest.write_char(']')),
            &CloseCurlyBracket => try!(dest.write_char('}')),
        }
        Ok(())
    }
}

pub fn serialize_identifier<W>(value: &str, dest: &mut W) -> text_writer::Result
where W:TextWriter {
    // TODO: avoid decoding/re-encoding UTF-8?
    let mut iter = value.chars();
    let mut c = iter.next().unwrap();
    if c == '-' {
        c = match iter.next() {
            None => return dest.write_str("\\-"),
            Some(c) => { try!(dest.write_char('-')); c },
        }
    };
    try!(serialize_char(c, dest, /* is_identifier_start = */ true));
    for c in iter {
        try!(serialize_char(c, dest, /* is_identifier_start = */ false));
    }
    Ok(())
}

#[inline]
fn serialize_char<W>(c: char, dest: &mut W, is_identifier_start: bool) -> text_writer::Result
where W: TextWriter {
    match c {
        '0'...'9' if is_identifier_start => try!(dest.write_str(format!("\\3{} ", c).as_slice())),
        '-' if is_identifier_start => try!(dest.write_str("\\-")),
        '0'...'9' | 'A'...'Z' | 'a'...'z' | '_' | '-' => try!(dest.write_char(c)),
        _ if c > '\x7F' => try!(dest.write_char(c)),
        '\n' => try!(dest.write_str("\\A ")),
        '\r' => try!(dest.write_str("\\D ")),
        '\x0C' => try!(dest.write_str("\\C ")),
        _ => { try!(dest.write_char('\\')); try!(dest.write_char(c)) },
    };
    Ok(())
}

pub fn serialize_string<W>(value: &str, dest: &mut W) -> text_writer::Result
where W: TextWriter {
    try!(dest.write_char('"'));
    // TODO: avoid decoding/re-encoding UTF-8?
    for c in value.chars() {
        match c {
            '"' => try!(dest.write_str("\\\"")),
            '\\' => try!(dest.write_str("\\\\")),
            '\n' => try!(dest.write_str("\\A ")),
            '\r' => try!(dest.write_str("\\D ")),
            '\x0C' => try!(dest.write_str("\\C ")),
            _ => try!(dest.write_char(c)),
        };
    }
    dest.write_char('"')
}

impl<'a> ToCss for [ComponentValue] {
    fn to_css<W>(&self, dest: &mut W) -> text_writer::Result where W: TextWriter {
        component_values_to_css(self.iter(), dest)
    }
}

impl<'a> ToCss for [Node] {
    fn to_css<W>(&self, dest: &mut W) -> text_writer::Result where W: TextWriter {
        let component_values = self.iter().map(|n| match n { &(ref c, _) => c });
        component_values_to_css(component_values, dest)
    }
}

fn component_values_to_css<'a, I, W>(mut iter: I, dest: &mut W) -> text_writer::Result
where I: Iterator<&'a ComponentValue>, W: TextWriter {
    let mut previous = match iter.next() {
        None => return Ok(()),
        Some(first) => { try!(first.to_css(dest)); first }
    };
    macro_rules! matches(
        ($value:expr, $($pattern:pat)|+) => (
            match $value { $($pattern)|+ => true, _ => false }
        );
    )
    // This does not borrow-check: for component_value in iter {
    loop { match iter.next() { None => break, Some(component_value) => {
        let (a, b) = (previous, component_value);
        if (
            matches!(*a, Ident(..) | AtKeyword(..) | Hash(..) | IDHash(..) |
                         Dimension(..) | Delim('#') | Delim('-') | Number(..)) &&
            matches!(*b, Ident(..) | Function(..) | URL(..) | BadURL(..) |
                         Number(..) | Percentage(..) | Dimension(..) | UnicodeRange(..))
        ) || (
            matches!(*a, Ident(..)) &&
            matches!(*b, ParenthesisBlock(..))
        ) || (
            matches!(*a, Ident(..) | AtKeyword(..) | Hash(..) | IDHash(..) | Dimension(..)) &&
            matches!(*b, Delim('-') | CDC)
        ) || (
            matches!(*a, Delim('#') | Delim('-') | Number(..) | Delim('@')) &&
            matches!(*b, Ident(..) | Function(..) | URL(..) | BadURL(..))
        ) || (
            matches!(*a, Delim('@')) &&
            matches!(*b, Ident(..) | Function(..) | URL(..) | BadURL(..) |
                         UnicodeRange(..) | Delim('-'))
        ) || (
            matches!(*a, UnicodeRange(..) | Delim('.') | Delim('+')) &&
            matches!(*b, Number(..) | Percentage(..) | Dimension(..))
        ) || (
            matches!(*a, UnicodeRange(..)) &&
            matches!(*b, Ident(..) | Function(..) | Delim('?'))
        ) || (match (a, b) { (&Delim(a), &Delim(b)) => matches!((a, b),
            ('#', '-') |
            ('$', '=') |
            ('*', '=') |
            ('^', '=') |
            ('~', '=') |
            ('|', '=') |
            ('|', '|') |
            ('/', '*')
        ), _ => false }) {
            try!(dest.write_str("/**/"));
        }
        // Skip whitespace when '\n' was previously written at the previous iteration.
        if !matches!((previous, component_value), (&Delim('\\'), &WhiteSpace)) {
            try!(component_value.to_css(dest));
        }
        if component_value == &Delim('\\') {
            try!(dest.write_char('\n'));
        }
        previous = component_value;
    }}}
    Ok(())
}

impl ToCss for Declaration {
    fn to_css<W>(&self, dest: &mut W) -> text_writer::Result where W: TextWriter {
        try!(dest.write_str(self.name.as_slice()));
        try!(dest.write_char(':'));
        try!(self.value.to_css(dest));
        Ok(())
    }
}

impl ToCss for [Declaration] {
    fn to_css<W>(&self, dest: &mut W) -> text_writer::Result where W: TextWriter {
        for declaration in self.iter() {
            try!(declaration.to_css(dest));
            try!(dest.write_char(';'));
        }
        Ok(())
    }
}

impl ToCss for QualifiedRule {
    fn to_css<W>(&self, dest: &mut W) -> text_writer::Result where W: TextWriter {
        try!(self.prelude.to_css(dest));
        try!(dest.write_char('{'));
        try!(self.block.to_css(dest));
        try!(dest.write_char('}'));
        Ok(())
    }
}

impl ToCss for AtRule {
    fn to_css<W>(&self, dest: &mut W) -> text_writer::Result where W: TextWriter {
        try!(dest.write_char('@'));
        try!(dest.write_str(self.name.as_slice()));
        try!(self.prelude.to_css(dest));
        match self.block {
            Some(ref block) => {
                try!(dest.write_char('{'));
                try!(block.to_css(dest));
                try!(dest.write_char('}'));
            }
            None => try!(dest.write_char(';'))
        }
        Ok(())
    }
}

impl ToCss for DeclarationListItem {
    fn to_css<W>(&self, dest: &mut W) -> text_writer::Result where W: TextWriter {
        match self {
            &DeclarationListItem::Declaration(ref declaration) => declaration.to_css(dest),
            &DeclarationListItem::AtRule(ref at_rule) => at_rule.to_css(dest),
        }
    }
}

impl ToCss for [DeclarationListItem] {
    fn to_css<W>(&self, dest: &mut W) -> text_writer::Result where W: TextWriter {
        for item in self.iter() {
            try!(item.to_css(dest));
            match item {
                &DeclarationListItem::AtRule(_) => {}
                &DeclarationListItem::Declaration(_) => try!(dest.write_char(';'))
            }
        }
        Ok(())
    }
}

impl ToCss for Rule {
    fn to_css<W>(&self, dest: &mut W) -> text_writer::Result where W: TextWriter {
        match self {
            &Rule::QualifiedRule(ref rule) => rule.to_css(dest),
            &Rule::AtRule(ref rule) => rule.to_css(dest),
        }
    }
}
<|endoftext|>