<commit_before>use crate::{
    transform::{MirPass, MirSource},
    util::patch::MirPatch,
};
use rustc_middle::mir::*;
use rustc_middle::ty::{Ty, TyCtxt};
use std::{borrow::Cow, fmt::Debug};

use super::simplify::simplify_cfg;

/// This pass optimizes something like
/// ```text
/// let x: Option<()>;
/// let y: Option<()>;
/// match (x,y) {
///     (Some(_), Some(_)) => {0},
///     _ => {1}
/// }
/// ```
/// into something like
/// ```text
/// let x: Option<()>;
/// let y: Option<()>;
/// let discriminant_x = // get discriminant of x
/// let discriminant_y = // get discriminant of y
/// if discriminant_x != discriminant_y || discriminant_x == None {1} else {0}
/// ```
pub struct EarlyOtherwiseBranch;

impl<'tcx> MirPass<'tcx> for EarlyOtherwiseBranch {
    fn run_pass(&self, tcx: TyCtxt<'tcx>, source: MirSource<'tcx>, body: &mut Body<'tcx>) {
        if tcx.sess.opts.debugging_opts.mir_opt_level < 3 {
            return;
        }
        trace!("running EarlyOtherwiseBranch on {:?}", source);
        // we are only interested in this bb if the terminator is a switchInt
        let bbs_with_switch =
            body.basic_blocks().iter_enumerated().filter(|(_, bb)| is_switch(bb.terminator()));

        let opts_to_apply: Vec<OptimizationToApply<'tcx>> = bbs_with_switch
            .flat_map(|(bb_idx, bb)| {
                let switch = bb.terminator();
                let helper = Helper { body, tcx };
                let infos = helper.go(bb, switch)?;
                Some(OptimizationToApply { infos, basic_block_first_switch: bb_idx })
            })
            .collect();

        let should_cleanup = !opts_to_apply.is_empty();

        for opt_to_apply in opts_to_apply {
            trace!("SUCCESS: found optimization possibility to apply: {:?}", &opt_to_apply);

            let statements_before =
                body.basic_blocks()[opt_to_apply.basic_block_first_switch].statements.len();
            let end_of_block_location = Location {
                block: opt_to_apply.basic_block_first_switch,
                statement_index: statements_before,
            };

            let mut patch = MirPatch::new(body);

            // create temp to store second discriminant in
            let discr_type = opt_to_apply.infos[0].second_switch_info.discr_ty;
            let discr_span = opt_to_apply.infos[0].second_switch_info.discr_source_info.span;
            let second_discriminant_temp = patch.new_temp(discr_type, discr_span);

            patch.add_statement(
                end_of_block_location,
                StatementKind::StorageLive(second_discriminant_temp),
            );

            // create assignment of discriminant
            let place_of_adt_to_get_discriminant_of =
                opt_to_apply.infos[0].second_switch_info.place_of_adt_discr_read;
            patch.add_assign(
                end_of_block_location,
                Place::from(second_discriminant_temp),
                Rvalue::Discriminant(place_of_adt_to_get_discriminant_of),
            );

            // create temp to store NotEqual comparison between the two discriminants
            let not_equal = BinOp::Ne;
            let not_equal_res_type = not_equal.ty(tcx, discr_type, discr_type);
            let not_equal_temp = patch.new_temp(not_equal_res_type, discr_span);
            patch.add_statement(end_of_block_location, StatementKind::StorageLive(not_equal_temp));

            // create NotEqual comparison between the two discriminants
            let first_descriminant_place =
                opt_to_apply.infos[0].first_switch_info.discr_used_in_switch;
            let not_equal_rvalue = Rvalue::BinaryOp(
                not_equal,
                Operand::Copy(Place::from(second_discriminant_temp)),
                Operand::Copy(Place::from(first_descriminant_place)),
            );
            patch.add_statement(
                end_of_block_location,
                StatementKind::Assign(box (Place::from(not_equal_temp), not_equal_rvalue)),
            );

            let (mut targets_to_jump_to, values_to_jump_to): (Vec<_>, Vec<_>) = opt_to_apply
                .infos
                .iter()
                .flat_map(|x| x.second_switch_info.targets_with_values.iter())
                .cloned()
                .unzip();

            // add otherwise case in the end
            targets_to_jump_to.push(opt_to_apply.infos[0].first_switch_info.otherwise_bb);
            // new block that jumps to the correct discriminant case. This block is switched to if the discriminants are equal
            let new_switch_data = BasicBlockData::new(Some(Terminator {
                source_info: opt_to_apply.infos[0].second_switch_info.discr_source_info,
                kind: TerminatorKind::SwitchInt {
                    // the first and second discriminants are equal, so just pick one
                    discr: Operand::Copy(first_descriminant_place),
                    switch_ty: discr_type,
                    values: Cow::from(values_to_jump_to),
                    targets: targets_to_jump_to,
                },
            }));

            let new_switch_bb = patch.new_block(new_switch_data);

            // switch on the NotEqual. If true, then jump to the `otherwise` case.
            // If false, then jump to a basic block that then jumps to the correct disciminant case
            let true_case = opt_to_apply.infos[0].first_switch_info.otherwise_bb;
            let false_case = new_switch_bb;
            patch.patch_terminator(
                opt_to_apply.basic_block_first_switch,
                TerminatorKind::if_(
                    tcx,
                    Operand::Move(Place::from(not_equal_temp)),
                    true_case,
                    false_case,
                ),
            );

            // generate StorageDead for the second_discriminant_temp not in use anymore
            patch.add_statement(
                end_of_block_location,
                StatementKind::StorageDead(second_discriminant_temp),
            );

            // Generate a StorageDead for not_equal_temp in each of the targets, since we moved it into the switch
            for bb in [false_case, true_case].iter() {
                patch.add_statement(
                    Location { block: *bb, statement_index: 0 },
                    StatementKind::StorageDead(not_equal_temp),
                );
            }

            patch.apply(body);
        }

        // Since this optimization adds new basic blocks and invalidates others,
        // clean up the cfg to make it nicer for other passes
        if should_cleanup {
            simplify_cfg(body);
        }
    }
}

fn is_switch<'tcx>(terminator: &Terminator<'tcx>) -> bool {
    match terminator.kind {
        TerminatorKind::SwitchInt { .. } => true,
        _ => false,
    }
}

struct Helper<'a, 'tcx> {
    body: &'a Body<'tcx>,
    tcx: TyCtxt<'tcx>,
}

#[derive(Debug, Clone)]
struct SwitchDiscriminantInfo<'tcx> {
    /// Type of the discriminant being switched on
    discr_ty: Ty<'tcx>,
    /// The basic block that the otherwise branch points to
    otherwise_bb: BasicBlock,
    /// Target along with the value being branched from. Otherwise is not included
    targets_with_values: Vec<(BasicBlock, u128)>,
    discr_source_info: SourceInfo,
    /// The place of the discriminant used in the switch
    discr_used_in_switch: Place<'tcx>,
    /// The place of the adt that has its discriminant read
    place_of_adt_discr_read: Place<'tcx>,
    /// The type of the adt that has its discriminant read
    type_adt_matched_on: Ty<'tcx>,
}

#[derive(Debug)]
struct OptimizationToApply<'tcx> {
    infos: Vec<OptimizationInfo<'tcx>>,
    /// Basic block of the original first switch
    basic_block_first_switch: BasicBlock,
}

#[derive(Debug)]
struct OptimizationInfo<'tcx> {
    /// Info about the first switch and discriminant
    first_switch_info: SwitchDiscriminantInfo<'tcx>,
    /// Info about the second switch and discriminant
    second_switch_info: SwitchDiscriminantInfo<'tcx>,
}

impl<'a, 'tcx> Helper<'a, 'tcx> {
    pub fn go(
        &self,
        bb: &BasicBlockData<'tcx>,
        switch: &Terminator<'tcx>,
    ) -> Option<Vec<OptimizationInfo<'tcx>>> {
        // try to find the statement that defines the discriminant that is used for the switch
        let discr = self.find_switch_discriminant_info(bb, switch)?;

        // go through each target, finding a discriminant read, and a switch
        let results = discr.targets_with_values.iter().map(|(target, value)| {
            self.find_discriminant_switch_pairing(&discr, target.clone(), value.clone())
        });

        // if the optimization did not apply for one of the targets, then abort
        if results.clone().any(|x| x.is_none()) || results.len() == 0 {
            trace!("NO: not all of the targets matched the pattern for optimization");
            return None;
        }

        Some(results.flatten().collect())
    }

    fn find_discriminant_switch_pairing(
        &self,
        discr_info: &SwitchDiscriminantInfo<'tcx>,
        target: BasicBlock,
        value: u128,
    ) -> Option<OptimizationInfo<'tcx>> {
        let bb = &self.body.basic_blocks()[target];
        // find switch
        let terminator = bb.terminator();
        if is_switch(terminator) {
            let this_bb_discr_info = self.find_switch_discriminant_info(bb, terminator)?;

            // the types of the two adts matched on have to be equalfor this optimization to apply
            if discr_info.type_adt_matched_on != this_bb_discr_info.type_adt_matched_on {
                trace!(
                    "NO: types do not match. LHS: {:?}, RHS: {:?}",
                    discr_info.type_adt_matched_on,
                    this_bb_discr_info.type_adt_matched_on
                );
                return None;
            }

            // the otherwise branch of the two switches have to point to the same bb
            if discr_info.otherwise_bb != this_bb_discr_info.otherwise_bb {
                trace!("NO: otherwise target is not the same");
                return None;
            }

            // check that the value being matched on is the same. The
            if this_bb_discr_info.targets_with_values.iter().find(|x| x.1 == value).is_none() {
                trace!("NO: values being matched on are not the same");
                return None;
            }

            // only allow optimization if the left and right of the tuple being matched are the same variants.
            // so the following should not optimize
            //  ```rust
            // let x: Option<()>;
            // let y: Option<()>;
            // match (x,y) {
            //     (Some(_), None) => {},
            //     _ => {}
            // }
            //  ```
            // We check this by seeing that the value of the first discriminant is the only other discriminant value being used as a target in the second switch
            if !(this_bb_discr_info.targets_with_values.len() == 1
                && this_bb_discr_info.targets_with_values[0].1 == value)
            {
                trace!(
                    "NO: The second switch did not have only 1 target (besides otherwise) that had the same value as the value from the first switch that got us here"
                );
                return None;
            }

            // if we reach this point, the optimization applies, and we should be able to optimize this case
            // store the info that is needed to apply the optimization

            Some(OptimizationInfo {
                first_switch_info: discr_info.clone(),
                second_switch_info: this_bb_discr_info,
            })
        } else {
            None
        }
    }

    fn find_switch_discriminant_info(
        &self,
        bb: &BasicBlockData<'tcx>,
        switch: &Terminator<'tcx>,
    ) -> Option<SwitchDiscriminantInfo<'tcx>> {
        match &switch.kind {
            TerminatorKind::SwitchInt { discr, targets, values, .. } => {
                let discr_local = discr.place()?.as_local()?;
                // the declaration of the discriminant read. Place of this read is being used in the switch
                let discr_decl = &self.body.local_decls()[discr_local];
                let discr_ty = discr_decl.ty;
                // the otherwise target lies as the last element
                let otherwise_bb = targets.get(values.len())?.clone();
                let targets_with_values = targets
                    .iter()
                    .zip(values.iter())
                    .map(|(t, v)| (t.clone(), v.clone()))
                    .collect();

                // find the place of the adt where the discriminant is being read from
                // assume this is the last statement of the block
                let place_of_adt_discr_read = match bb.statements.last()?.kind {
                    StatementKind::Assign(box (_, Rvalue::Discriminant(adt_place))) => {
                        Some(adt_place)
                    }
                    _ => None,
                }?;

                let type_adt_matched_on = place_of_adt_discr_read.ty(self.body, self.tcx).ty;

                Some(SwitchDiscriminantInfo {
                    discr_used_in_switch: discr.place()?,
                    discr_ty,
                    otherwise_bb,
                    targets_with_values,
                    discr_source_info: discr_decl.source_info,
                    place_of_adt_discr_read,
                    type_adt_matched_on,
                })
            }
            _ => unreachable!("must only be passed terminator that is a switch"),
        }
    }
}
<commit_msg>enable on mir-opt-level=1 to test perf<commit_after>use crate::{
    transform::{MirPass, MirSource},
    util::patch::MirPatch,
};
use rustc_middle::mir::*;
use rustc_middle::ty::{Ty, TyCtxt};
use std::{borrow::Cow, fmt::Debug};

use super::simplify::simplify_cfg;

/// This pass optimizes something like
/// ```text
/// let x: Option<()>;
/// let y: Option<()>;
/// match (x,y) {
///     (Some(_), Some(_)) => {0},
///     _ => {1}
/// }
/// ```
/// into something like
/// ```text
/// let x: Option<()>;
/// let y: Option<()>;
/// let discriminant_x = // get discriminant of x
/// let discriminant_y = // get discriminant of y
/// if discriminant_x != discriminant_y || discriminant_x == None {1} else {0}
/// ```
pub struct EarlyOtherwiseBranch;

impl<'tcx> MirPass<'tcx> for EarlyOtherwiseBranch {
    fn run_pass(&self, tcx: TyCtxt<'tcx>, source: MirSource<'tcx>, body: &mut Body<'tcx>) {
        if tcx.sess.opts.debugging_opts.mir_opt_level < 1 {
            return;
        }
        trace!("running EarlyOtherwiseBranch on {:?}", source);
        // we are only interested in this bb if the terminator is a switchInt
        let bbs_with_switch =
            body.basic_blocks().iter_enumerated().filter(|(_, bb)| is_switch(bb.terminator()));

        let opts_to_apply: Vec<OptimizationToApply<'tcx>> = bbs_with_switch
            .flat_map(|(bb_idx, bb)| {
                let switch = bb.terminator();
                let helper = Helper { body, tcx };
                let infos = helper.go(bb, switch)?;
                Some(OptimizationToApply { infos, basic_block_first_switch: bb_idx })
            })
            .collect();

        let should_cleanup = !opts_to_apply.is_empty();

        for opt_to_apply in opts_to_apply {
            trace!("SUCCESS: found optimization possibility to apply: {:?}", &opt_to_apply);

            let statements_before =
                body.basic_blocks()[opt_to_apply.basic_block_first_switch].statements.len();
            let end_of_block_location = Location {
                block: opt_to_apply.basic_block_first_switch,
                statement_index: statements_before,
            };

            let mut patch = MirPatch::new(body);

            // create temp to store second discriminant in
            let discr_type = opt_to_apply.infos[0].second_switch_info.discr_ty;
            let discr_span = opt_to_apply.infos[0].second_switch_info.discr_source_info.span;
            let second_discriminant_temp = patch.new_temp(discr_type, discr_span);

            patch.add_statement(
                end_of_block_location,
                StatementKind::StorageLive(second_discriminant_temp),
            );

            // create assignment of discriminant
            let place_of_adt_to_get_discriminant_of =
                opt_to_apply.infos[0].second_switch_info.place_of_adt_discr_read;
            patch.add_assign(
                end_of_block_location,
                Place::from(second_discriminant_temp),
                Rvalue::Discriminant(place_of_adt_to_get_discriminant_of),
            );

            // create temp to store NotEqual comparison between the two discriminants
            let not_equal = BinOp::Ne;
            let not_equal_res_type = not_equal.ty(tcx, discr_type, discr_type);
            let not_equal_temp = patch.new_temp(not_equal_res_type, discr_span);
            patch.add_statement(end_of_block_location, StatementKind::StorageLive(not_equal_temp));

            // create NotEqual comparison between the two discriminants
            let first_descriminant_place =
                opt_to_apply.infos[0].first_switch_info.discr_used_in_switch;
            let not_equal_rvalue = Rvalue::BinaryOp(
                not_equal,
                Operand::Copy(Place::from(second_discriminant_temp)),
                Operand::Copy(Place::from(first_descriminant_place)),
            );
            patch.add_statement(
                end_of_block_location,
                StatementKind::Assign(box (Place::from(not_equal_temp), not_equal_rvalue)),
            );

            let (mut targets_to_jump_to, values_to_jump_to): (Vec<_>, Vec<_>) = opt_to_apply
                .infos
                .iter()
                .flat_map(|x| x.second_switch_info.targets_with_values.iter())
                .cloned()
                .unzip();

            // add otherwise case in the end
            targets_to_jump_to.push(opt_to_apply.infos[0].first_switch_info.otherwise_bb);
            // new block that jumps to the correct discriminant case. This block is switched to if the discriminants are equal
            let new_switch_data = BasicBlockData::new(Some(Terminator {
                source_info: opt_to_apply.infos[0].second_switch_info.discr_source_info,
                kind: TerminatorKind::SwitchInt {
                    // the first and second discriminants are equal, so just pick one
                    discr: Operand::Copy(first_descriminant_place),
                    switch_ty: discr_type,
                    values: Cow::from(values_to_jump_to),
                    targets: targets_to_jump_to,
                },
            }));

            let new_switch_bb = patch.new_block(new_switch_data);

            // switch on the NotEqual. If true, then jump to the `otherwise` case.
            // If false, then jump to a basic block that then jumps to the correct disciminant case
            let true_case = opt_to_apply.infos[0].first_switch_info.otherwise_bb;
            let false_case = new_switch_bb;
            patch.patch_terminator(
                opt_to_apply.basic_block_first_switch,
                TerminatorKind::if_(
                    tcx,
                    Operand::Move(Place::from(not_equal_temp)),
                    true_case,
                    false_case,
                ),
            );

            // generate StorageDead for the second_discriminant_temp not in use anymore
            patch.add_statement(
                end_of_block_location,
                StatementKind::StorageDead(second_discriminant_temp),
            );

            // Generate a StorageDead for not_equal_temp in each of the targets, since we moved it into the switch
            for bb in [false_case, true_case].iter() {
                patch.add_statement(
                    Location { block: *bb, statement_index: 0 },
                    StatementKind::StorageDead(not_equal_temp),
                );
            }

            patch.apply(body);
        }

        // Since this optimization adds new basic blocks and invalidates others,
        // clean up the cfg to make it nicer for other passes
        if should_cleanup {
            simplify_cfg(body);
        }
    }
}

fn is_switch<'tcx>(terminator: &Terminator<'tcx>) -> bool {
    match terminator.kind {
        TerminatorKind::SwitchInt { .. } => true,
        _ => false,
    }
}

struct Helper<'a, 'tcx> {
    body: &'a Body<'tcx>,
    tcx: TyCtxt<'tcx>,
}

#[derive(Debug, Clone)]
struct SwitchDiscriminantInfo<'tcx> {
    /// Type of the discriminant being switched on
    discr_ty: Ty<'tcx>,
    /// The basic block that the otherwise branch points to
    otherwise_bb: BasicBlock,
    /// Target along with the value being branched from. Otherwise is not included
    targets_with_values: Vec<(BasicBlock, u128)>,
    discr_source_info: SourceInfo,
    /// The place of the discriminant used in the switch
    discr_used_in_switch: Place<'tcx>,
    /// The place of the adt that has its discriminant read
    place_of_adt_discr_read: Place<'tcx>,
    /// The type of the adt that has its discriminant read
    type_adt_matched_on: Ty<'tcx>,
}

#[derive(Debug)]
struct OptimizationToApply<'tcx> {
    infos: Vec<OptimizationInfo<'tcx>>,
    /// Basic block of the original first switch
    basic_block_first_switch: BasicBlock,
}

#[derive(Debug)]
struct OptimizationInfo<'tcx> {
    /// Info about the first switch and discriminant
    first_switch_info: SwitchDiscriminantInfo<'tcx>,
    /// Info about the second switch and discriminant
    second_switch_info: SwitchDiscriminantInfo<'tcx>,
}

impl<'a, 'tcx> Helper<'a, 'tcx> {
    pub fn go(
        &self,
        bb: &BasicBlockData<'tcx>,
        switch: &Terminator<'tcx>,
    ) -> Option<Vec<OptimizationInfo<'tcx>>> {
        // try to find the statement that defines the discriminant that is used for the switch
        let discr = self.find_switch_discriminant_info(bb, switch)?;

        // go through each target, finding a discriminant read, and a switch
        let results = discr.targets_with_values.iter().map(|(target, value)| {
            self.find_discriminant_switch_pairing(&discr, target.clone(), value.clone())
        });

        // if the optimization did not apply for one of the targets, then abort
        if results.clone().any(|x| x.is_none()) || results.len() == 0 {
            trace!("NO: not all of the targets matched the pattern for optimization");
            return None;
        }

        Some(results.flatten().collect())
    }

    fn find_discriminant_switch_pairing(
        &self,
        discr_info: &SwitchDiscriminantInfo<'tcx>,
        target: BasicBlock,
        value: u128,
    ) -> Option<OptimizationInfo<'tcx>> {
        let bb = &self.body.basic_blocks()[target];
        // find switch
        let terminator = bb.terminator();
        if is_switch(terminator) {
            let this_bb_discr_info = self.find_switch_discriminant_info(bb, terminator)?;

            // the types of the two adts matched on have to be equalfor this optimization to apply
            if discr_info.type_adt_matched_on != this_bb_discr_info.type_adt_matched_on {
                trace!(
                    "NO: types do not match. LHS: {:?}, RHS: {:?}",
                    discr_info.type_adt_matched_on,
                    this_bb_discr_info.type_adt_matched_on
                );
                return None;
            }

            // the otherwise branch of the two switches have to point to the same bb
            if discr_info.otherwise_bb != this_bb_discr_info.otherwise_bb {
                trace!("NO: otherwise target is not the same");
                return None;
            }

            // check that the value being matched on is the same. The
            if this_bb_discr_info.targets_with_values.iter().find(|x| x.1 == value).is_none() {
                trace!("NO: values being matched on are not the same");
                return None;
            }

            // only allow optimization if the left and right of the tuple being matched are the same variants.
            // so the following should not optimize
            //  ```rust
            // let x: Option<()>;
            // let y: Option<()>;
            // match (x,y) {
            //     (Some(_), None) => {},
            //     _ => {}
            // }
            //  ```
            // We check this by seeing that the value of the first discriminant is the only other discriminant value being used as a target in the second switch
            if !(this_bb_discr_info.targets_with_values.len() == 1
                && this_bb_discr_info.targets_with_values[0].1 == value)
            {
                trace!(
                    "NO: The second switch did not have only 1 target (besides otherwise) that had the same value as the value from the first switch that got us here"
                );
                return None;
            }

            // if we reach this point, the optimization applies, and we should be able to optimize this case
            // store the info that is needed to apply the optimization

            Some(OptimizationInfo {
                first_switch_info: discr_info.clone(),
                second_switch_info: this_bb_discr_info,
            })
        } else {
            None
        }
    }

    fn find_switch_discriminant_info(
        &self,
        bb: &BasicBlockData<'tcx>,
        switch: &Terminator<'tcx>,
    ) -> Option<SwitchDiscriminantInfo<'tcx>> {
        match &switch.kind {
            TerminatorKind::SwitchInt { discr, targets, values, .. } => {
                let discr_local = discr.place()?.as_local()?;
                // the declaration of the discriminant read. Place of this read is being used in the switch
                let discr_decl = &self.body.local_decls()[discr_local];
                let discr_ty = discr_decl.ty;
                // the otherwise target lies as the last element
                let otherwise_bb = targets.get(values.len())?.clone();
                let targets_with_values = targets
                    .iter()
                    .zip(values.iter())
                    .map(|(t, v)| (t.clone(), v.clone()))
                    .collect();

                // find the place of the adt where the discriminant is being read from
                // assume this is the last statement of the block
                let place_of_adt_discr_read = match bb.statements.last()?.kind {
                    StatementKind::Assign(box (_, Rvalue::Discriminant(adt_place))) => {
                        Some(adt_place)
                    }
                    _ => None,
                }?;

                let type_adt_matched_on = place_of_adt_discr_read.ty(self.body, self.tcx).ty;

                Some(SwitchDiscriminantInfo {
                    discr_used_in_switch: discr.place()?,
                    discr_ty,
                    otherwise_bb,
                    targets_with_values,
                    discr_source_info: discr_decl.source_info,
                    place_of_adt_discr_read,
                    type_adt_matched_on,
                })
            }
            _ => unreachable!("must only be passed terminator that is a switch"),
        }
    }
}
<|endoftext|>


<commit_before>#![feature(globs)]
#![feature(macro_rules)]

extern crate deuterium;

use deuterium::*;

macro_rules! assert_sql(
    ($query:expr, $s:expr) => (
        assert_eq!($query.to_final_sql().as_slice(), $s)
    )
)

#[test]
fn it_works() {

    let jedi_table = TableDef::new("jedi".to_string());
    let name = NamedField::<String>::new("name");

    // Type is here only for sure it is right, it can be ommited in real code
    let query: SelectQuery<(String), LimitMany> = jedi_table.select_1(&name).where_(
        name.is("Luke".to_string()).exclude()
    );

    assert_sql!(query, "SELECT name FROM jedi WHERE name != 'Luke';");

}

#[test]
fn select_1_first() {

    let jedi_table = TableDef::new("jedi".to_string());
    let name = NamedField::<String>::new("name");

    let query: SelectQuery<(String), LimitOne> = jedi_table.select_1(&name).where_(
        name.is("Luke".to_string()).exclude()
    ).first().offset(10);

    assert_sql!(query, "SELECT name FROM jedi WHERE name != 'Luke' LIMIT 1 OFFSET 10;");

}

#[test]
fn select_order() {

    let jedi_table = TableDef::new("jedi".to_string());
    let name = NamedField::<String>::new("name");

    let query: SelectQuery<(String), LimitOne> = jedi_table.select_1(&name)
        .first().order_by(&name);

    assert_sql!(query, "SELECT name FROM jedi ORDER BY name ASC LIMIT 1;");

}

#[test]
fn select_within() {

    let jedi_table = TableDef::new("jedi".to_string());
    let name = NamedField::<String>::new("name");

    let query = jedi_table.select_all().where_(name.within(vec!["Luke".to_string()]));
    assert_sql!(query, "SELECT * FROM jedi WHERE name IN ('Luke');");

}

#[test]
fn select_within_select() {

    let jedi_table = TableDef::new("jedi".to_string());
    let name = NamedField::<String>::new("name");

    let query = jedi_table.alias("j".to_string()).select_all().where_(name.within(
        jedi_table.select_1(&name)
    ));

    assert_sql!(query, "SELECT * FROM jedi AS j WHERE name IN (SELECT name FROM jedi);");

}

#[test]
fn select_from_select() {

    let jedi_table = TableDef::new("jedi".to_string());

    let query = jedi_table.select_all().alias("jedi_list".to_string()).select_all();
    assert_sql!(query, "SELECT * FROM (SELECT * FROM jedi) as jedi_list;");

}

#[test]
fn select_left_join() {

    let jedi_table = TableDef::new("jedi".to_string());
    let name = NamedField::<String>::new("name");

    let query = jedi_table.select_all().left_join(&jedi_table.alias("j".to_string()), name.is(name.clone()));
    assert_sql!(query, "SELECT * FROM jedi LEFT JOIN jedi AS j ON name = name;");

}
<commit_msg>feat: aliases<commit_after>#![feature(globs)]
#![feature(macro_rules)]

extern crate deuterium;

use deuterium::*;

macro_rules! assert_sql(
    ($query:expr, $s:expr) => (
        assert_eq!($query.to_final_sql().as_slice(), $s)
    )
)

#[test]
fn it_works() {

    let jedi_table = TableDef::new("jedi".to_string());
    let name = NamedField::<String>::new("name");

    // Type is here only for sure it is right, it can be ommited in real code
    let query: SelectQuery<(String), LimitMany> = jedi_table.select_1(&name).where_(
        name.is("Luke".to_string()).exclude()
    );

    assert_sql!(query, "SELECT name FROM jedi WHERE name != 'Luke';");

}

#[test]
fn select_1_first() {

    let jedi_table = TableDef::new("jedi".to_string());
    let name = NamedField::<String>::new("name");

    let query: SelectQuery<(String), LimitOne> = jedi_table.select_1(&name).where_(
        name.is("Luke".to_string()).exclude()
    ).first().offset(10);

    assert_sql!(query, "SELECT name FROM jedi WHERE name != 'Luke' LIMIT 1 OFFSET 10;");

}

#[test]
fn select_order() {

    let jedi_table = TableDef::new("jedi".to_string());
    let name = NamedField::<String>::new("name");

    let query: SelectQuery<(String), LimitOne> = jedi_table.select_1(&name)
        .first().order_by(&name);

    assert_sql!(query, "SELECT name FROM jedi ORDER BY name ASC LIMIT 1;");

}

#[test]
fn select_within() {

    let jedi_table = TableDef::new("jedi".to_string());
    let name = NamedField::<String>::new("name");

    let query = jedi_table.select_all().where_(name.within(vec!["Luke".to_string()]));
    assert_sql!(query, "SELECT * FROM jedi WHERE name IN ('Luke');");

}

#[test]
fn select_within_select() {

    let jedi_table = TableDef::new("jedi".to_string());
    let name = NamedField::<String>::new("name");

    let query = jedi_table.alias("j".to_string()).select_all().where_(name.within(
        jedi_table.select_1(&name)
    ));

    assert_sql!(query, "SELECT * FROM jedi AS j WHERE name IN (SELECT name FROM jedi);");

}

#[test]
fn select_from_select() {

    let jedi_table = TableDef::new("jedi".to_string());

    let query = jedi_table.select_all().alias("jedi_list".to_string()).select_all();
    assert_sql!(query, "SELECT * FROM (SELECT * FROM jedi) as jedi_list;");

}

#[test]
fn select_left_join() {

    let jedi_table = TableDef::new("jedi".to_string());
    let name = NamedField::<String>::new("name");

    let query = jedi_table.select_all().left_join(&jedi_table.alias("j".to_string()), name.is(name.clone()));
    assert_sql!(query, "SELECT * FROM jedi LEFT JOIN jedi AS j ON name = name;");

}

#[test]
fn aliases() {

    let jedi_table = TableDef::new("jedi".to_string());
    let jedi_a = jedi_table.alias("a".to_string());
    let jedi_b = jedi_table.alias("b".to_string());
    let name_a = NamedField::<String>::new("name").qual("a");
    let name_b = NamedField::<String>::new("name").qual("b");

    let query = jedi_a.select_all().inner_join(&jedi_b, name_a.is(name_b));
    assert_sql!(query, "SELECT * FROM jedi AS a INNER JOIN jedi AS b ON a.name = b.name;");

}
<|endoftext|>


<commit_before>use std::io::{File, BufferedReader};
use std::io::SeekStyle::SeekSet;
use error::{CTResult, CTDatabaseError, CTSyntaxError};
use error::CTError::{DatabaseError, SyntaxError};
use token::Token;
use token::TokenKind::Elem;

#[derive(Show, PartialEq)]
pub struct ElemData {
    short_name: String,
    long_name: String,
    mass: f64,
    atomic_num: u16,
}

pub struct ElemDatabase {
    db: BufferedReader<File>,
}

impl ElemDatabase {
    pub fn open(path: &str) -> CTResult<ElemDatabase> {
        match File::open(&Path::new(path)) {
            Ok(db_file) => Ok(ElemDatabase { db: BufferedReader::new(db_file) }),
            Err(_) => Err(DatabaseError(CTDatabaseError {
                desc: format!("Could not open database file. Expected at: {:?}", path)
            })),
        }
    }

    pub fn get_single_data(&mut self, elem: &Token) -> CTResult<ElemData> {
        // we return to the beginning of the underlying file, since the data
        // might lie on a line we have previously read past
        self.db.get_mut().seek(0, SeekSet);
        let mut line_iter = self.db.lines();
        let short_name = match elem.tok {
            Elem(ref name) => name.as_slice(),
            _ => unreachable!(),
        };
        // TODO: Make this cleaner, and avoid returns
        loop {
            match line_iter.next() {
                Some(Ok(ref line)) if line.starts_with(short_name) => return decode_line(line),
                Some(Err(_)) => return Err(DatabaseError(CTDatabaseError {
                    desc: "Error reading the database".to_string()
                })),
                None => return Err(SyntaxError(CTSyntaxError {
                    desc: format!("Could not find element: {:?}", short_name),
                    pos: elem.pos,
                    len: elem.len,
                })),
                _ => (),
            }
        }
    }
}

pub fn decode_line(line: &String) -> CTResult<ElemData> {
    let data: Vec<&str> = line.trim().split(';').collect();
    if data.len() < 4 {
        Err(DatabaseError(CTDatabaseError {
            desc: "Missing field in database".to_string()
        }))
    } else {
        let mass = data[1].parse::<f64>();
        let atomic_num = data[3].parse::<u16>();
        if let (Some(m), Some(an)) = (mass, atomic_num) {
            Ok(ElemData {
                short_name: data[0].to_string(),
                long_name: data[2].to_string(),
                mass: m,
                atomic_num: an,
            })
        } else {
            Err(DatabaseError(CTDatabaseError {
                desc: "Field in database corrupted".to_string()
            }))
        }
    }
}

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

    #[test]
    fn decode() {
        let raw_result = decode_line(&"A;123.456789;Abba;12".to_string());
        let expected = ElemData {
            short_name: "A".to_string(),
            long_name: "Abba".to_string(),
            mass: 123.456789,
            atomic_num: 12,
        };
        assert_eq!(Ok(expected), raw_result);
    }

    #[test]
    fn missing_field() {
        assert!(decode_line(&"A;".to_string()).is_err());
    }

    #[test]
    fn field_corrupted() {
        assert!(decode_line(&"A;not a number;Abba;12".to_string()).is_err());
    }
}<commit_msg>Explicitly handle Result in get_single_data<commit_after>use std::io::{File, BufferedReader};
use std::io::SeekStyle::SeekSet;
use error::{CTResult, CTDatabaseError, CTSyntaxError};
use error::CTError::{DatabaseError, SyntaxError};
use token::Token;
use token::TokenKind::Elem;

#[derive(Show, PartialEq)]
pub struct ElemData {
    short_name: String,
    long_name: String,
    mass: f64,
    atomic_num: u16,
}

pub struct ElemDatabase {
    db: BufferedReader<File>,
}

impl ElemDatabase {
    pub fn open(path: &str) -> CTResult<ElemDatabase> {
        match File::open(&Path::new(path)) {
            Ok(db_file) => Ok(ElemDatabase { db: BufferedReader::new(db_file) }),
            Err(_) => Err(DatabaseError(CTDatabaseError {
                desc: format!("Could not open database file. Expected at: {:?}", path)
            })),
        }
    }

    pub fn get_single_data(&mut self, elem: &Token) -> CTResult<ElemData> {
        // we return to the beginning of the underlying file, since the data
        // might lie on a line we have previously read past
        self.db.get_mut().seek(0, SeekSet).ok().expect("Internal error reading database");
        let mut line_iter = self.db.lines();
        let short_name = match elem.tok {
            Elem(ref name) => name.as_slice(),
            _ => unreachable!(),
        };
        // TODO: Make this cleaner, and avoid returns
        loop {
            match line_iter.next() {
                Some(Ok(ref line)) if line.starts_with(short_name) => return decode_line(line),
                Some(Err(_)) => return Err(DatabaseError(CTDatabaseError {
                    desc: "Error reading the database".to_string()
                })),
                None => return Err(SyntaxError(CTSyntaxError {
                    desc: format!("Could not find element: {:?}", short_name),
                    pos: elem.pos,
                    len: elem.len,
                })),
                _ => (),
            }
        }
    }
}

pub fn decode_line(line: &String) -> CTResult<ElemData> {
    let data: Vec<&str> = line.trim().split(';').collect();
    if data.len() < 4 {
        Err(DatabaseError(CTDatabaseError {
            desc: "Missing field in database".to_string()
        }))
    } else {
        let mass = data[1].parse::<f64>();
        let atomic_num = data[3].parse::<u16>();
        if let (Some(m), Some(an)) = (mass, atomic_num) {
            Ok(ElemData {
                short_name: data[0].to_string(),
                long_name: data[2].to_string(),
                mass: m,
                atomic_num: an,
            })
        } else {
            Err(DatabaseError(CTDatabaseError {
                desc: "Field in database corrupted".to_string()
            }))
        }
    }
}

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

    #[test]
    fn decode() {
        let raw_result = decode_line(&"A;123.456789;Abba;12".to_string());
        let expected = ElemData {
            short_name: "A".to_string(),
            long_name: "Abba".to_string(),
            mass: 123.456789,
            atomic_num: 12,
        };
        assert_eq!(Ok(expected), raw_result);
    }

    #[test]
    fn missing_field() {
        assert!(decode_line(&"A;".to_string()).is_err());
    }

    #[test]
    fn field_corrupted() {
        assert!(decode_line(&"A;not a number;Abba;12".to_string()).is_err());
    }
}<|endoftext|>


<commit_before>//! Data structures containing extracted URL, mention, tag, and media information.
//!
//! These structures are meant to be received in an API call to describe the data they accompany.
//! For example, a `UrlEntity` describes a hyperlink in a tweet or user description text, and a
//! `HashtagEntity` describes a hashtag or stock symbol extracted from a tweet.
//!
//! For more information on the data in these structures, see Twitter's documentation for
//! [Entities][] and [Entities in Objects][obj].
//!
//! [Entities]: https://dev.twitter.com/overview/api/entities
//! [obj]: https://dev.twitter.com/overview/api/entities-in-twitter-objects
//!
//! ## Using indices and URLs
//!
//! When displaying text with accompanied entities, be wary about how you use the accompanied
//! indices. The indices given by Twitter reference the *graphemes* in the tweet, so something like
//! `char_indices` will fall flat when faced with text that uses combining characters. The
//! [unicode-segmentation][] crate provides a means to iterate over the graphemes of a string,
//! allowing you to make sure you hyperlink the right range of characters in the text.
//!
//! [unicode-segmentation]: https://crates.io/crates/unicode-segmentation
//!
//! Alternately, when substituting URLs for display, `str::replace` works just fine.
use common::*;
use error;
use error::Error::InvalidResponse;
use rustc_serialize::json;
use mime;

///Represents a hashtag or symbol extracted from another piece of text.
#[derive(Debug)]
pub struct HashtagEntity {
    ///The character indices where the hashtag is located. The first index is the location of the #
    ///or $ character; the second is the location of the first character following the hashtag.
    pub indices: (i32, i32),
    ///The text of the hashtag, without the leading # or $ character.
    pub text: String,
}

///Represents a piece of media attached to a tweet.
///
///The information in this struct is subtly different depending on what media is being referenced,
///and which entity container is holding this instance. For videos and GIFs, the `media_url` and
///`media_url_https` fields each link to a thumbnail image of the media, typically of the first
///frame. The real video information can be found on the `video_info` field, including various
///encodings if available.
///
///Image links available in `media_url` and `media_url_https` can be obtained in different sizes by
///appending a colon and one of the available sizes in the `MediaSizes` struct. For example, the
///cropped thumbnail can be viewed by appending `:thumb` to the end of the URL, and the full-size
///image can be viewed by appending `:large`.
#[derive(Debug)]
pub struct MediaEntity {
    ///A shortened URL to display to clients.
    pub display_url: String,
    ///An expanded version of `display_url`; links to the media display page.
    pub expanded_url: String,
    ///A numeric ID for the media.
    pub id: i64,
    ///Character indices where the media URL is located. The first index is the location of the
    ///first character of the URL; the second is the location of the first character following the
    ///URL.
    pub indices: (i32, i32),
    ///A URL pointing directly to the media file. Uses HTTP as the protocol.
    ///
    ///For videos and GIFs, this link will be to a thumbnail of the media, and the real video link
    ///will be contained in `video_info`.
    pub media_url: String,
    ///A URL pointing directly to the media file. Uses HTTPS as the protocol.
    ///
    ///For videos and GIFs, this link will be to a thumbnail of the media, and the real video link
    ///will be contained in `video_info`.
    pub media_url_https: String,
    ///Various sizes available for the media file.
    pub sizes: MediaSizes,
    ///For tweets containing media that was originally associated with a different tweet, this
    ///contains the ID of the original tweet.
    pub source_status_id: Option<i64>,
    ///The type of media being represented.
    pub media_type: MediaType,
    ///The t.co link from the original text.
    pub url: String,
    ///For media entities corresponding to videos, this contains extra information about the linked
    ///video.
    pub video_info: Option<VideoInfo>,
}

///Represents the types of media that can be attached to a tweet.
#[derive(Debug)]
pub enum MediaType {
    ///A static image.
    Photo,
    ///A video.
    Video,
    ///An animated GIF, delivered as a video without audio.
    Gif,
}

///Represents the available sizes for a media file.
#[derive(Debug)]
pub struct MediaSizes {
    ///Information for a thumbnail-sized version of the media.
    pub thumb: MediaSize,
    ///Information for a small-sized version of the media.
    pub small: MediaSize,
    ///Information for a medium-sized version of the media.
    pub medium: MediaSize,
    ///Information for a large-sized version of the media.
    pub large: MediaSize,
}

///Represents how an image has been resized for a given size variant.
#[derive(Debug)]
pub enum ResizeMode {
    ///The media was resized to fit one dimension, keeping its aspect ratio.
    Fit,
    ///The media was cropped to fit a specific resolution.
    Crop,
}

///Represents the dimensions of a media file.
#[derive(Debug)]
pub struct MediaSize {
    ///The size variant's width in pixels.
    pub w: i32,
    ///The size variant's height in pixels.
    pub h: i32,
    ///The method used to obtain the given dimensions.
    pub resize: ResizeMode,
}

///Represents metadata specific to videos.
#[derive(Debug)]
pub struct VideoInfo {
    ///The aspect ratio of the video.
    pub aspect_ratio: (i32, i32),
    ///The duration of the video, in milliseconds.
    ///
    ///This field is not given for animated GIFs.
    pub duration_millis: Option<i32>,
    ///Information about various encodings available for the video.
    pub variants: Vec<VideoVariant>,
}

///Represents information about a specific encoding of a video.
#[derive(Debug)]
pub struct VideoVariant {
    ///The bitrate of the video. This value is present for GIFs, but it will be zero.
    pub bitrate: Option<i32>,
    ///The file format of the video variant.
    pub content_type: mime::Mime,
    ///The URL for the video variant.
    pub url: String,
}

///Represents a link extracted from another piece of text.
#[derive(Debug)]
pub struct UrlEntity {
    ///A truncated URL meant to be displayed inline with the text.
    pub display_url: String,
    ///The URL that the t.co URL resolves to.
    ///
    ///Meant to be used as hover-text when a user mouses over a link.
    pub expanded_url: String,
    ///The character positions in the companion text the URL was extracted from.
    pub indices: (i32, i32),
    ///The t.co URL extracted from the companion text.
    pub url: String,
}

///Represnts a user mention extracted from another piece of text.
#[derive(Debug)]
pub struct MentionEntity {
    ///Numeric ID of the mentioned user.
    pub id: i64,
    ///Character indices where the user mention is located in the original text. The first index is
    ///the location of the @ symbol; the second is the location of the first character following
    ///the user screen name.
    pub indices: (i32, i32),
    ///Display name of the mentioned user.
    pub name: String,
    ///Screen name of the mentioned user, without the leading @ symbol.
    pub screen_name: String,
}

impl FromJson for HashtagEntity {
    fn from_json(input: &json::Json) -> Result<Self, error::Error> {
        if !input.is_object() {
            return Err(InvalidResponse("HashtagEntity received json that wasn't an object", Some(input.to_string())));
        }

        Ok(HashtagEntity {
            indices: try!(field(input, "indices")),
            text: try!(field(input, "text")),
        })
    }
}

impl FromJson for MediaEntity {
    fn from_json(input: &json::Json) -> Result<Self, error::Error> {
        if !input.is_object() {
            return Err(InvalidResponse("MediaEntity received json that wasn't an object", Some(input.to_string())));
        }

        Ok(MediaEntity {
            display_url: try!(field(input, "display_url")),
            expanded_url: try!(field(input, "expanded_url")),
            id: try!(field(input, "id")),
            indices: try!(field(input, "indices")),
            media_url: try!(field(input, "media_url")),
            media_url_https: try!(field(input, "media_url_https")),
            sizes: try!(field(input, "sizes")),
            source_status_id: field(input, "source_status_id").ok(),
            media_type: try!(field(input, "type")),
            url: try!(field(input, "url")),
            video_info: None,
        })
    }
}

impl FromJson for MediaType {
    fn from_json(input: &json::Json) -> Result<Self, error::Error> {
        if let Some(s) = input.as_string() {
            if s == "photo" {
                Ok(MediaType::Photo)
            }
            else if s == "video" {
                Ok(MediaType::Video)
            }
            else if s == "animated_gif" {
                Ok(MediaType::Gif)
            }
            else {
                Err(InvalidResponse("unexpected string for MediaType", Some(s.to_string())))
            }
        }
        else {
            Err(InvalidResponse("MediaType received json that wasn't a string", Some(input.to_string())))
        }
    }
}

impl FromJson for ResizeMode {
    fn from_json(input: &json::Json) -> Result<Self, error::Error> {
        if let Some(s) = input.as_string() {
            if s == "fit" {
                Ok(ResizeMode::Fit)
            }
            else if s == "crop" {
                Ok(ResizeMode::Crop)
            }
            else {
                Err(InvalidResponse("unexpected string for ResizeMode", Some(s.to_string())))
            }
        }
        else {
            Err(InvalidResponse("ResizeMode received json that wasn't an object", Some(input.to_string())))
        }
    }
}

impl FromJson for MediaSize {
    fn from_json(input: &json::Json) -> Result<Self, error::Error> {
        if !input.is_object() {
            return Err(InvalidResponse("MediaSize received json that wasn't an object", Some(input.to_string())));
        }

        Ok(MediaSize {
            w: try!(field(input, "w")),
            h: try!(field(input, "h")),
            resize: try!(field(input, "resize")),
        })
    }
}

impl FromJson for MediaSizes {
    fn from_json(input: &json::Json) -> Result<Self, error::Error> {
        if !input.is_object() {
            return Err(InvalidResponse("MediaSizes received json that wasn't an object", Some(input.to_string())));
        }

        Ok(MediaSizes {
            thumb: try!(field(input, "thumb")),
            small: try!(field(input, "small")),
            medium: try!(field(input, "medium")),
            large: try!(field(input, "large")),
        })
    }
}

impl FromJson for UrlEntity {
    fn from_json(input: &json::Json) -> Result<Self, error::Error> {
        if !input.is_object() {
            return Err(InvalidResponse("UrlEntity received json that wasn't an object", Some(input.to_string())));
        }

        Ok(UrlEntity {
            display_url: try!(field(input, "display_url")),
            expanded_url: try!(field(input, "expanded_url")),
            indices: try!(field(input, "indices")),
            url: try!(field(input, "url")),
        })
    }
}

impl FromJson for VideoInfo {
    fn from_json(input: &json::Json) -> Result<Self, error::Error> {
        if !input.is_object() {
            return Err(InvalidResponse("VideoInfo received json that wasn't an object", Some(input.to_string())));
        }

        Ok(VideoInfo {
            aspect_ratio: try!(field(input, "aspect_ratio")),
            duration_millis: field(input, "duration_millis").ok(),
            variants: try!(field(input, "variants")),
        })
    }
}

impl FromJson for VideoVariant {
    fn from_json(input: &json::Json) -> Result<Self, error::Error> {
        if !input.is_object() {
            return Err(InvalidResponse("VideoVariant received json that wasn't an object", Some(input.to_string())));
        }

        Ok(VideoVariant {
            bitrate: field(input, "bitrate").ok(),
            content_type: try!(field(input, "content_type")),
            url: try!(field(input, "url")),
        })
    }
}

impl FromJson for MentionEntity {
    fn from_json(input: &json::Json) -> Result<Self, error::Error> {
        if !input.is_object() {
            return Err(InvalidResponse("MentionEntity received json that wasn't an object", Some(input.to_string())));
        }

        Ok(MentionEntity {
            id: try!(field(input, "id")),
            indices: try!(field(input, "indices")),
            name: try!(field(input, "name")),
            screen_name: try!(field(input, "screen_name")),
        })
    }
}
<commit_msg>extra entity docs about urls<commit_after>//! Data structures containing extracted URL, mention, tag, and media information.
//!
//! These structures are meant to be received in an API call to describe the data they accompany.
//! For example, a `UrlEntity` describes a hyperlink in a tweet or user description text, and a
//! `HashtagEntity` describes a hashtag or stock symbol extracted from a tweet.
//!
//! For more information on the data in these structures, see Twitter's documentation for
//! [Entities][] and [Entities in Objects][obj].
//!
//! [Entities]: https://dev.twitter.com/overview/api/entities
//! [obj]: https://dev.twitter.com/overview/api/entities-in-twitter-objects
//!
//! ## Using indices and URLs
//!
//! When displaying text with accompanied entities, be wary about how you use the accompanied
//! indices. The indices given by Twitter reference the *graphemes* in the tweet, so something like
//! `char_indices` will fall flat when faced with text that uses combining characters. The
//! [unicode-segmentation][] crate provides a means to iterate over the graphemes of a string,
//! allowing you to make sure you hyperlink the right range of characters in the text.
//!
//! [unicode-segmentation]: https://crates.io/crates/unicode-segmentation
//!
//! Alternately, when substituting URLs for display, `str::replace` works just fine.
//!
//! ### Shortened, Display, and Expanded URLs
//!
//! URL and Media entities contain references to a URL within their parent text. However, due to
//! the nature of how Twitter handles URLs in tweets and user bios, each entity struct has three
//! URLs within it:
//!
//! - `url`: This is the `t.co` shortened URL as returned directly from twitter. This is what
//!   contributes to character count in tweets and user bios.
//! - `expanded_url`: This is the original URL the user entered in their tweet. While it is given
//!   to API client, Twitter recommends still sending users to the shortened link, for analytics
//!   purposes. Twitter Web uses this field to supply hover-text for where the URL resolves to.
//! - `display_url`: This is a truncated version of `expanded_url`, meant to be displayed inline
//!   with the parent text. This is useful to show users where the link resolves to, without
//!   potentially filling up a lot of space with the fullly expanded URL.
use common::*;
use error;
use error::Error::InvalidResponse;
use rustc_serialize::json;
use mime;

///Represents a hashtag or symbol extracted from another piece of text.
#[derive(Debug)]
pub struct HashtagEntity {
    ///The character indices where the hashtag is located. The first index is the location of the #
    ///or $ character; the second is the location of the first character following the hashtag.
    pub indices: (i32, i32),
    ///The text of the hashtag, without the leading # or $ character.
    pub text: String,
}

///Represents a piece of media attached to a tweet.
///
///The information in this struct is subtly different depending on what media is being referenced,
///and which entity container is holding this instance. For videos and GIFs, the `media_url` and
///`media_url_https` fields each link to a thumbnail image of the media, typically of the first
///frame. The real video information can be found on the `video_info` field, including various
///encodings if available.
///
///Image links available in `media_url` and `media_url_https` can be obtained in different sizes by
///appending a colon and one of the available sizes in the `MediaSizes` struct. For example, the
///cropped thumbnail can be viewed by appending `:thumb` to the end of the URL, and the full-size
///image can be viewed by appending `:large`.
#[derive(Debug)]
pub struct MediaEntity {
    ///A shortened URL to display to clients.
    pub display_url: String,
    ///An expanded version of `display_url`; links to the media display page.
    pub expanded_url: String,
    ///A numeric ID for the media.
    pub id: i64,
    ///Character indices where the media URL is located. The first index is the location of the
    ///first character of the URL; the second is the location of the first character following the
    ///URL.
    pub indices: (i32, i32),
    ///A URL pointing directly to the media file. Uses HTTP as the protocol.
    ///
    ///For videos and GIFs, this link will be to a thumbnail of the media, and the real video link
    ///will be contained in `video_info`.
    pub media_url: String,
    ///A URL pointing directly to the media file. Uses HTTPS as the protocol.
    ///
    ///For videos and GIFs, this link will be to a thumbnail of the media, and the real video link
    ///will be contained in `video_info`.
    pub media_url_https: String,
    ///Various sizes available for the media file.
    pub sizes: MediaSizes,
    ///For tweets containing media that was originally associated with a different tweet, this
    ///contains the ID of the original tweet.
    pub source_status_id: Option<i64>,
    ///The type of media being represented.
    pub media_type: MediaType,
    ///The t.co link from the original text.
    pub url: String,
    ///For media entities corresponding to videos, this contains extra information about the linked
    ///video.
    pub video_info: Option<VideoInfo>,
}

///Represents the types of media that can be attached to a tweet.
#[derive(Debug)]
pub enum MediaType {
    ///A static image.
    Photo,
    ///A video.
    Video,
    ///An animated GIF, delivered as a video without audio.
    Gif,
}

///Represents the available sizes for a media file.
#[derive(Debug)]
pub struct MediaSizes {
    ///Information for a thumbnail-sized version of the media.
    pub thumb: MediaSize,
    ///Information for a small-sized version of the media.
    pub small: MediaSize,
    ///Information for a medium-sized version of the media.
    pub medium: MediaSize,
    ///Information for a large-sized version of the media.
    pub large: MediaSize,
}

///Represents how an image has been resized for a given size variant.
#[derive(Debug)]
pub enum ResizeMode {
    ///The media was resized to fit one dimension, keeping its aspect ratio.
    Fit,
    ///The media was cropped to fit a specific resolution.
    Crop,
}

///Represents the dimensions of a media file.
#[derive(Debug)]
pub struct MediaSize {
    ///The size variant's width in pixels.
    pub w: i32,
    ///The size variant's height in pixels.
    pub h: i32,
    ///The method used to obtain the given dimensions.
    pub resize: ResizeMode,
}

///Represents metadata specific to videos.
#[derive(Debug)]
pub struct VideoInfo {
    ///The aspect ratio of the video.
    pub aspect_ratio: (i32, i32),
    ///The duration of the video, in milliseconds.
    ///
    ///This field is not given for animated GIFs.
    pub duration_millis: Option<i32>,
    ///Information about various encodings available for the video.
    pub variants: Vec<VideoVariant>,
}

///Represents information about a specific encoding of a video.
#[derive(Debug)]
pub struct VideoVariant {
    ///The bitrate of the video. This value is present for GIFs, but it will be zero.
    pub bitrate: Option<i32>,
    ///The file format of the video variant.
    pub content_type: mime::Mime,
    ///The URL for the video variant.
    pub url: String,
}

///Represents a link extracted from another piece of text.
#[derive(Debug)]
pub struct UrlEntity {
    ///A truncated URL meant to be displayed inline with the text.
    pub display_url: String,
    ///The URL that the t.co URL resolves to.
    ///
    ///Meant to be used as hover-text when a user mouses over a link.
    pub expanded_url: String,
    ///The character positions in the companion text the URL was extracted from.
    pub indices: (i32, i32),
    ///The t.co URL extracted from the companion text.
    pub url: String,
}

///Represnts a user mention extracted from another piece of text.
#[derive(Debug)]
pub struct MentionEntity {
    ///Numeric ID of the mentioned user.
    pub id: i64,
    ///Character indices where the user mention is located in the original text. The first index is
    ///the location of the @ symbol; the second is the location of the first character following
    ///the user screen name.
    pub indices: (i32, i32),
    ///Display name of the mentioned user.
    pub name: String,
    ///Screen name of the mentioned user, without the leading @ symbol.
    pub screen_name: String,
}

impl FromJson for HashtagEntity {
    fn from_json(input: &json::Json) -> Result<Self, error::Error> {
        if !input.is_object() {
            return Err(InvalidResponse("HashtagEntity received json that wasn't an object", Some(input.to_string())));
        }

        Ok(HashtagEntity {
            indices: try!(field(input, "indices")),
            text: try!(field(input, "text")),
        })
    }
}

impl FromJson for MediaEntity {
    fn from_json(input: &json::Json) -> Result<Self, error::Error> {
        if !input.is_object() {
            return Err(InvalidResponse("MediaEntity received json that wasn't an object", Some(input.to_string())));
        }

        Ok(MediaEntity {
            display_url: try!(field(input, "display_url")),
            expanded_url: try!(field(input, "expanded_url")),
            id: try!(field(input, "id")),
            indices: try!(field(input, "indices")),
            media_url: try!(field(input, "media_url")),
            media_url_https: try!(field(input, "media_url_https")),
            sizes: try!(field(input, "sizes")),
            source_status_id: field(input, "source_status_id").ok(),
            media_type: try!(field(input, "type")),
            url: try!(field(input, "url")),
            video_info: None,
        })
    }
}

impl FromJson for MediaType {
    fn from_json(input: &json::Json) -> Result<Self, error::Error> {
        if let Some(s) = input.as_string() {
            if s == "photo" {
                Ok(MediaType::Photo)
            }
            else if s == "video" {
                Ok(MediaType::Video)
            }
            else if s == "animated_gif" {
                Ok(MediaType::Gif)
            }
            else {
                Err(InvalidResponse("unexpected string for MediaType", Some(s.to_string())))
            }
        }
        else {
            Err(InvalidResponse("MediaType received json that wasn't a string", Some(input.to_string())))
        }
    }
}

impl FromJson for ResizeMode {
    fn from_json(input: &json::Json) -> Result<Self, error::Error> {
        if let Some(s) = input.as_string() {
            if s == "fit" {
                Ok(ResizeMode::Fit)
            }
            else if s == "crop" {
                Ok(ResizeMode::Crop)
            }
            else {
                Err(InvalidResponse("unexpected string for ResizeMode", Some(s.to_string())))
            }
        }
        else {
            Err(InvalidResponse("ResizeMode received json that wasn't an object", Some(input.to_string())))
        }
    }
}

impl FromJson for MediaSize {
    fn from_json(input: &json::Json) -> Result<Self, error::Error> {
        if !input.is_object() {
            return Err(InvalidResponse("MediaSize received json that wasn't an object", Some(input.to_string())));
        }

        Ok(MediaSize {
            w: try!(field(input, "w")),
            h: try!(field(input, "h")),
            resize: try!(field(input, "resize")),
        })
    }
}

impl FromJson for MediaSizes {
    fn from_json(input: &json::Json) -> Result<Self, error::Error> {
        if !input.is_object() {
            return Err(InvalidResponse("MediaSizes received json that wasn't an object", Some(input.to_string())));
        }

        Ok(MediaSizes {
            thumb: try!(field(input, "thumb")),
            small: try!(field(input, "small")),
            medium: try!(field(input, "medium")),
            large: try!(field(input, "large")),
        })
    }
}

impl FromJson for UrlEntity {
    fn from_json(input: &json::Json) -> Result<Self, error::Error> {
        if !input.is_object() {
            return Err(InvalidResponse("UrlEntity received json that wasn't an object", Some(input.to_string())));
        }

        Ok(UrlEntity {
            display_url: try!(field(input, "display_url")),
            expanded_url: try!(field(input, "expanded_url")),
            indices: try!(field(input, "indices")),
            url: try!(field(input, "url")),
        })
    }
}

impl FromJson for VideoInfo {
    fn from_json(input: &json::Json) -> Result<Self, error::Error> {
        if !input.is_object() {
            return Err(InvalidResponse("VideoInfo received json that wasn't an object", Some(input.to_string())));
        }

        Ok(VideoInfo {
            aspect_ratio: try!(field(input, "aspect_ratio")),
            duration_millis: field(input, "duration_millis").ok(),
            variants: try!(field(input, "variants")),
        })
    }
}

impl FromJson for VideoVariant {
    fn from_json(input: &json::Json) -> Result<Self, error::Error> {
        if !input.is_object() {
            return Err(InvalidResponse("VideoVariant received json that wasn't an object", Some(input.to_string())));
        }

        Ok(VideoVariant {
            bitrate: field(input, "bitrate").ok(),
            content_type: try!(field(input, "content_type")),
            url: try!(field(input, "url")),
        })
    }
}

impl FromJson for MentionEntity {
    fn from_json(input: &json::Json) -> Result<Self, error::Error> {
        if !input.is_object() {
            return Err(InvalidResponse("MentionEntity received json that wasn't an object", Some(input.to_string())));
        }

        Ok(MentionEntity {
            id: try!(field(input, "id")),
            indices: try!(field(input, "indices")),
            name: try!(field(input, "name")),
            screen_name: try!(field(input, "screen_name")),
        })
    }
}
<|endoftext|>


<commit_before>//! Extension methods for `docker_compose::v2::File`.

use docker_compose::v2 as dc;
#[cfg(test)] use std::io;
#[cfg(test)] use std::path::Path;

#[cfg(test)] use default_tags::DefaultTags;
use ext::service::ServiceExt;
use project::Project;
#[cfg(test)] use util::ConductorPathExt;
use util::Error;

/// These methods will appear as regular methods on `dc::File` in any module
/// which includes `FileExt`.
pub trait FileExt {
    /// Make any local updates to this file we want to make before
    /// outputting it for `Project::output`.
    fn update_for_output(&mut self, project: &Project) -> Result<(), Error>;
}

impl FileExt for dc::File {
    fn update_for_output(&mut self, project: &Project) -> Result<(), Error> {
        for (_name, mut service) in self.services.iter_mut() {
            try!(service.update_for_output(project));
        }
        Ok(())
    }
}

#[test]
fn update_for_output_mounts_cloned_source() {
    use docker_compose::v2 as dc;
    use env_logger;
    let _ = env_logger::init();

    let cursor = io::Cursor::new("dockercloud/hello-world:staging\n");
    let default_tags = DefaultTags::read(cursor).unwrap();

    let mut proj = Project::from_example("hello").unwrap();
    proj.set_default_tags(default_tags);
    let repo = proj.repos().find_by_alias("dockercloud-hello-world").unwrap();
    repo.fake_clone_source(&proj).unwrap();
    proj.output().unwrap();

    // Load the generated file and look at the `web` service we cloned.
    let frontend_file = proj.output_dir().join("pods/frontend.yml");
    let file = dc::File::read_from_path(frontend_file).unwrap();
    let web = file.services.get("web").unwrap();
    let src_path = repo.path(&proj).to_absolute().unwrap();

    // Make sure our `build` entry has been pointed at the local source
    // directory.
    assert_eq!(web.build.as_ref().unwrap().context.value().unwrap(),
               &dc::Context::new(src_path.to_str().unwrap()));

    // Make sure the local source directory is being mounted into the
    // container.
    let mount = web.volumes.last()
        .expect("expected web service to have volumes")
        .value().unwrap();
    assert_eq!(mount.host, Some(dc::HostVolume::Path(src_path)));
    assert_eq!(mount.container, Path::new("/app"));

    // Make sure that our image versions were correctly defaulted.
    assert_eq!(web.image.as_ref().unwrap().value().unwrap(),
               &dc::Image::new("dockercloud/hello-world:staging").unwrap());

    proj.remove_test_output().unwrap();
}
<commit_msg>Hack up a slightly dubious system for per-pod configuration<commit_after>//! Extension methods for `docker_compose::v2::File`.

use docker_compose::v2 as dc;
use std::collections::BTreeSet;
#[cfg(test)] use std::io;
#[cfg(test)] use std::path::Path;

#[cfg(test)] use default_tags::DefaultTags;
use ext::service::ServiceExt;
use project::Project;
#[cfg(test)] use util::ConductorPathExt;
use util::Error;

/// These methods will appear as regular methods on `dc::File` in any module
/// which includes `FileExt`.
pub trait FileExt {
    /// Make any local updates to this file we want to make before
    /// outputting it for `Project::output`.
    fn update_for_output(&mut self, project: &Project) -> Result<(), Error>;

    /// Fetch a label associated with this pod.  For now, pod labels may be
    /// attached to any service in the pod as regular label, so long as
    /// there aren't conflicating values.  Returns `Ok(None)` if the label
    /// is not present on any service, or `Err(..)` if there are
    /// conflicting values.
    ///
    /// When `docker-compose.yml` adds support for custom top-level properties,
    /// we won't need this dubious hack any more.
    fn pod_label(&self, key: &str) -> Result<Option<String>, Error>;
}

impl FileExt for dc::File {
    fn update_for_output(&mut self, project: &Project) -> Result<(), Error> {
        for (_name, mut service) in self.services.iter_mut() {
            try!(service.update_for_output(project));
        }
        Ok(())
    }

    fn pod_label(&self, key: &str) -> Result<Option<String>, Error> {
        let mut set = BTreeSet::new();
        for (_name, service) in &self.services {
            if let Some(ref value) = service.labels.get(key) {
                set.insert(value.to_owned());
            }
        }
        match set.len() {
            0 => Ok(None),
            1 => {
                // Fetch the first and only item.
                Ok(Some(set.iter().cloned().next().unwrap().to_owned()))
            }
            _ => Err(err!("Conflicting values for {} in a single pod", key)),
        }
    }
}

#[test]
fn update_for_output_adds_tags_and_mounts_cloned_source() {
    use docker_compose::v2 as dc;
    use env_logger;
    let _ = env_logger::init();

    let cursor = io::Cursor::new("dockercloud/hello-world:staging\n");
    let default_tags = DefaultTags::read(cursor).unwrap();

    let mut proj = Project::from_example("hello").unwrap();
    proj.set_default_tags(default_tags);
    let repo = proj.repos().find_by_alias("dockercloud-hello-world").unwrap();
    repo.fake_clone_source(&proj).unwrap();
    proj.output().unwrap();

    // Load the generated file and look at the `web` service we cloned.
    let frontend_file = proj.output_dir().join("pods/frontend.yml");
    let file = dc::File::read_from_path(frontend_file).unwrap();
    let web = file.services.get("web").unwrap();
    let src_path = repo.path(&proj).to_absolute().unwrap();

    // Make sure our `build` entry has been pointed at the local source
    // directory.
    assert_eq!(web.build.as_ref().unwrap().context.value().unwrap(),
               &dc::Context::new(src_path.to_str().unwrap()));

    // Make sure the local source directory is being mounted into the
    // container.
    let mount = web.volumes.last()
        .expect("expected web service to have volumes")
        .value().unwrap();
    assert_eq!(mount.host, Some(dc::HostVolume::Path(src_path)));
    assert_eq!(mount.container, Path::new("/app"));

    // Make sure that our image versions were correctly defaulted.
    assert_eq!(web.image.as_ref().unwrap().value().unwrap(),
               &dc::Image::new("dockercloud/hello-world:staging").unwrap());

    proj.remove_test_output().unwrap();
}

#[test]
fn pod_label_fetches_label_from_service() {
    use env_logger;
    let _ = env_logger::init();

    let proj = Project::from_example("rails_hello").unwrap();
    let ovr = proj.ovr("development").unwrap();
    proj.output().unwrap();

    // Load the generated file and look at the `migrate` pod.
    let pod = proj.pod("migrate").unwrap();
    let file = pod.merged_file(&ovr).unwrap();
    assert_eq!(file.pod_label("io.fdy.conductor.type").unwrap().unwrap(),
               "task");
}
<|endoftext|>


<commit_before>use libc::{self, c_char, c_void, size_t};

use std::mem;
use std::ptr;

use num::{Integer, NumCast};
use num::traits::cast;
use std::ffi::{CStr, CString};

use error::H5Result;

pub fn string_from_cstr(string: *const c_char) -> String {
    unsafe {
        String::from_utf8_unchecked(CStr::from_ptr(string).to_bytes().to_vec())
    }
}

pub fn string_to_cstr<S>(string: S) -> *const c_char where S: Into<String> {
    unsafe {
        CString::from_vec_unchecked(string.into().into_bytes()).as_ptr()
    }
}

#[test]
fn test_string_cstr() {
    let s1: String = "foo".to_string();
    assert_eq!(s1, string_from_cstr(string_to_cstr(s1.clone())));
    let s2: &str = "bar";
    assert_eq!(s2, string_from_cstr(string_to_cstr(s2)));
}

pub fn get_h5_str<T, F>(func: F) -> H5Result<String>
                 where F: Fn(*mut c_char, size_t) -> T, T: Integer + NumCast {
    unsafe {
        let len: isize = 1 + cast::<T, isize>(func(ptr::null_mut::<c_char>(), 0)).unwrap();
        ensure!(len > 0, "negative string length in get_h5_str()");
        let buf = libc::malloc((len as size_t) * mem::size_of::<c_char>() as size_t) as *mut c_char;
        func(buf, len as size_t);
        let msg = string_from_cstr(buf);
        libc::free(buf as *mut c_void);
        Ok(msg)
    }
}

#[test]
pub fn test_get_h5_str() {
    use ffi::h5e::{H5E_type_t, H5Eget_msg, H5E_CANTOPENOBJ};
    let s = unsafe {
        get_h5_str(|msg, size| {
            H5Eget_msg(*H5E_CANTOPENOBJ, ptr::null_mut::<H5E_type_t>(), msg, size)
        }).ok().unwrap()
    };
    assert_eq!(s, "Can't open object");
}
<commit_msg>Remove redundant type annotations<commit_after>use libc::{self, c_char, c_void, size_t};

use std::mem;
use std::ptr;

use num::{Integer, NumCast};
use num::traits::cast;
use std::ffi::{CStr, CString};

use error::H5Result;

pub fn string_from_cstr(string: *const c_char) -> String {
    unsafe {
        String::from_utf8_unchecked(CStr::from_ptr(string).to_bytes().to_vec())
    }
}

pub fn string_to_cstr<S>(string: S) -> *const c_char where S: Into<String> {
    unsafe {
        CString::from_vec_unchecked(string.into().into_bytes()).as_ptr()
    }
}

#[test]
fn test_string_cstr() {
    let s1: String = "foo".to_string();
    assert_eq!(s1, string_from_cstr(string_to_cstr(s1.clone())));
    let s2: &str = "bar";
    assert_eq!(s2, string_from_cstr(string_to_cstr(s2)));
}

pub fn get_h5_str<T, F>(func: F) -> H5Result<String>
                 where F: Fn(*mut c_char, size_t) -> T, T: Integer + NumCast {
    unsafe {
        let len: isize = 1 + cast::<T, isize>(func(ptr::null_mut(), 0)).unwrap();
        ensure!(len > 0, "negative string length in get_h5_str()");
        let buf = libc::malloc((len as size_t) * mem::size_of::<c_char>() as size_t) as *mut c_char;
        func(buf, len as size_t);
        let msg = string_from_cstr(buf);
        libc::free(buf as *mut c_void);
        Ok(msg)
    }
}

#[test]
pub fn test_get_h5_str() {
    use ffi::h5e::{H5E_type_t, H5Eget_msg, H5E_CANTOPENOBJ};
    let s = unsafe {
        get_h5_str(|msg, size| {
            H5Eget_msg(*H5E_CANTOPENOBJ, ptr::null_mut(), msg, size)
        }).ok().unwrap()
    };
    assert_eq!(s, "Can't open object");
}
<|endoftext|>


<commit_before>use std::{mem, ptr};
use winapi::*;
use comptr::ComPtr;
use error::D2D1Error;
use math;

pub trait Geometry {
    unsafe fn get_ptr(&self) -> *mut ID2D1Geometry;

    /// Retrieve the bounds of the geometry, with an optional applied transform.
    ///
    /// **NOTE:** I'm not sure if this will ever return None, but the API has an
    /// error code so it could. The MSDN documentation is very vague on this.
    fn get_bounds(&self, world_transform: Option<&math::Matrix3x2F>) -> Result<math::RectF, D2D1Error> {
        unsafe {
            let ptr = self.get_ptr();
            assert!(!ptr.is_null());

            let matrix = match world_transform {
                Some(mat) => &mat.0 as *const _,
                None => ptr::null(),
            };

            let mut rect: D2D1_RECT_F = mem::uninitialized();
            let result = (*ptr).GetBounds(matrix, &mut rect);
            if SUCCEEDED(result) {
                Ok(math::RectF(rect))
            } else {
                Err(From::from(result))
            }
        }
    }

    /// Get the bounds of the corresponding geometry after it has been widened or have
    /// an optional pen style applied.
    // TODO: StrokeStyle
    fn get_widened_bounds(
        &self, stroke_width: f32, stroke_style: Option<()>,
        world_transform: Option<&math::Matrix3x2F>, flattening_tolerance: f32
    ) -> Result<math::RectF, D2D1Error> {
        unsafe {
            let ptr = self.get_ptr();
            assert!(!ptr.is_null());

            let matrix = match world_transform {
                Some(mat) => &mat.0 as *const _,
                None => ptr::null(),
            };
            let stroke_style = match stroke_style {
                Some(_) => ptr::null_mut(), // TODO
                None => ptr::null_mut(),
            };

            let mut rect: D2D1_RECT_F = mem::uninitialized();
            let result = (*ptr).GetWidenedBounds(
                stroke_width,
                stroke_style,
                matrix,
                flattening_tolerance,
                &mut rect,
            );

            if SUCCEEDED(result) {
                Ok(math::RectF(rect))
            } else {
                Err(From::from(result))
            }
        }
    }

    /// Checks to see whether the corresponding penned and widened geometry contains the
    /// given point.
    // TODO: StrokeStyle
    fn stroke_contains_point(
        &self, point: math::Point2F, stroke_width: f32, stroke_style: Option<()>,
        world_transform: Option<&math::Matrix3x2F>, flattening_tolerance: f32
    ) -> Result<bool, D2D1Error> {
        unsafe {
            let ptr = self.get_ptr();
            assert!(!ptr.is_null());

            let matrix = match world_transform {
                Some(mat) => &mat.0 as *const _,
                None => ptr::null(),
            };
            let stroke_style = match stroke_style {
                Some(_) => ptr::null_mut(), // TODO
                None => ptr::null_mut(),
            };

            let mut contains: BOOL = 0;
            let result = (*ptr).StrokeContainsPoint(
                point.0,
                stroke_width,
                stroke_style,
                matrix,
                flattening_tolerance,
                &mut contains,
            );

            if SUCCEEDED(result) {
                Ok(contains != 0)
            } else {
                Err(From::from(result))
            }
        }
    }

    fn fill_contains_point(
        &self, point: math::Point2F, world_transform: Option<&math::Matrix3x2F>,
        flattening_tolerance: f32
    )-> Result<bool, D2D1Error> {
        unsafe {
            let ptr = self.get_ptr();
            assert!(!ptr.is_null());

            let matrix = match world_transform {
                Some(mat) => &mat.0 as *const _,
                None => ptr::null(),
            };

            let mut contains: BOOL = 0;
            let result = (*ptr).FillContainsPoint(
                point.0,
                matrix,
                flattening_tolerance,
                &mut contains,
            );

            if SUCCEEDED(result) {
                Ok(contains != 0)
            } else {
                Err(From::from(result))
            }
        }
    }

    fn compare_with_geometry<T: Geometry>(
        &self, input: &T, input_transform: Option<&math::Matrix3x2F>, flattening_tolerance: f32
    ) -> Result<GeometryRelation, D2D1Error> {
        unsafe {
            let self_ptr = self.get_ptr();
            assert!(!self_ptr.is_null());
            let input_ptr = input.get_ptr();
            assert!(!input_ptr.is_null());

            let matrix = match input_transform {
                Some(mat) => &mat.0 as *const _,
                None => ptr::null(),
            };

            let mut relation: D2D1_GEOMETRY_RELATION = D2D1_GEOMETRY_RELATION_UNKNOWN;
            let result = (*self_ptr).CompareWithGeometry(
                input_ptr,
                matrix,
                flattening_tolerance,
                &mut relation,
            );

            if SUCCEEDED(result) {
                use self::GeometryRelation::*;
                match relation {
                    D2D1_GEOMETRY_RELATION_UNKNOWN => Ok(Unknown),
                    D2D1_GEOMETRY_RELATION_DISJOINT => Ok(Disjoint),
                    D2D1_GEOMETRY_RELATION_IS_CONTAINED => Ok(IsContained),
                    D2D1_GEOMETRY_RELATION_CONTAINS => Ok(Contains),
                    D2D1_GEOMETRY_RELATION_OVERLAP => Ok(Overlap),
                    _ => Err(D2D1Error::UnknownEnumValue),
                }
            } else {
                Err(From::from(result))
            }
        }
    }
}

pub enum GeometryRelation {
    Unknown = 0,
    Disjoint = 1,
    IsContained = 2,
    Contains = 3,
    Overlap = 4,
}

/// Represents a rectangle which can be used anywhere Geometry is needed
pub struct Rectangle {
    geom: ComPtr<ID2D1RectangleGeometry>,
}

impl Rectangle {
    pub fn get_rect(&self) -> math::RectF {
        unsafe {
            let mut rect: D2D1_RECT_F = mem::uninitialized();
            (*self.geom.raw_value()).GetRect(&mut rect);
            math::RectF(rect)
        }
    }
}

impl Geometry for Rectangle {
    unsafe fn get_ptr(&self) -> *mut ID2D1Geometry {
        &mut **(&mut *self.geom.raw_value())
    }
}

/// Represents a rounded rectangle which can be used anywhere Geometry is needed
pub struct RoundedRectangle {
    geom: ComPtr<ID2D1RoundedRectangleGeometry>,
}

impl Geometry for RoundedRectangle {
    unsafe fn get_ptr(&self) -> *mut ID2D1Geometry {
        &mut **(&mut *self.geom.raw_value())
    }
}

<commit_msg>Area and Length of geometry<commit_after>use std::{mem, ptr};
use winapi::*;
use comptr::ComPtr;
use error::D2D1Error;
use math;

pub trait Geometry {
    unsafe fn get_ptr(&self) -> *mut ID2D1Geometry;

    /// Retrieve the bounds of the geometry, with an optional applied transform.
    ///
    /// **NOTE:** I'm not sure if this will ever return None, but the API has an
    /// error code so it could. The MSDN documentation is very vague on this.
    fn get_bounds(&self, world_transform: Option<&math::Matrix3x2F>) -> Result<math::RectF, D2D1Error> {
        unsafe {
            let ptr = self.get_ptr();
            assert!(!ptr.is_null());

            let matrix = match world_transform {
                Some(mat) => &mat.0 as *const _,
                None => ptr::null(),
            };

            let mut rect: D2D1_RECT_F = mem::uninitialized();
            let result = (*ptr).GetBounds(matrix, &mut rect);
            if SUCCEEDED(result) {
                Ok(math::RectF(rect))
            } else {
                Err(From::from(result))
            }
        }
    }

    /// Get the bounds of the corresponding geometry after it has been widened or have
    /// an optional pen style applied.
    // TODO: StrokeStyle
    fn get_widened_bounds(
        &self, stroke_width: f32, stroke_style: Option<()>,
        world_transform: Option<&math::Matrix3x2F>, flattening_tolerance: f32
    ) -> Result<math::RectF, D2D1Error> {
        unsafe {
            let ptr = self.get_ptr();
            assert!(!ptr.is_null());

            let matrix = match world_transform {
                Some(mat) => &mat.0 as *const _,
                None => ptr::null(),
            };
            let stroke_style = match stroke_style {
                Some(_) => ptr::null_mut(), // TODO
                None => ptr::null_mut(),
            };

            let mut rect: D2D1_RECT_F = mem::uninitialized();
            let result = (*ptr).GetWidenedBounds(
                stroke_width,
                stroke_style,
                matrix,
                flattening_tolerance,
                &mut rect,
            );

            if SUCCEEDED(result) {
                Ok(math::RectF(rect))
            } else {
                Err(From::from(result))
            }
        }
    }

    /// Checks to see whether the corresponding penned and widened geometry contains the
    /// given point.
    // TODO: StrokeStyle
    fn stroke_contains_point(
        &self, point: math::Point2F, stroke_width: f32, stroke_style: Option<()>,
        world_transform: Option<&math::Matrix3x2F>, flattening_tolerance: f32
    ) -> Result<bool, D2D1Error> {
        unsafe {
            let ptr = self.get_ptr();
            assert!(!ptr.is_null());

            let matrix = match world_transform {
                Some(mat) => &mat.0 as *const _,
                None => ptr::null(),
            };
            let stroke_style = match stroke_style {
                Some(_) => ptr::null_mut(), // TODO
                None => ptr::null_mut(),
            };

            let mut contains: BOOL = 0;
            let result = (*ptr).StrokeContainsPoint(
                point.0,
                stroke_width,
                stroke_style,
                matrix,
                flattening_tolerance,
                &mut contains,
            );

            if SUCCEEDED(result) {
                Ok(contains != 0)
            } else {
                Err(From::from(result))
            }
        }
    }

    fn fill_contains_point(
        &self, point: math::Point2F, world_transform: Option<&math::Matrix3x2F>,
        flattening_tolerance: f32
    )-> Result<bool, D2D1Error> {
        unsafe {
            let ptr = self.get_ptr();
            assert!(!ptr.is_null());

            let matrix = match world_transform {
                Some(mat) => &mat.0 as *const _,
                None => ptr::null(),
            };

            let mut contains: BOOL = 0;
            let result = (*ptr).FillContainsPoint(
                point.0,
                matrix,
                flattening_tolerance,
                &mut contains,
            );

            if SUCCEEDED(result) {
                Ok(contains != 0)
            } else {
                Err(From::from(result))
            }
        }
    }

    fn compare_with_geometry<T: Geometry>(
        &self, input: &T, input_transform: Option<&math::Matrix3x2F>, flattening_tolerance: f32
    ) -> Result<GeometryRelation, D2D1Error> {
        unsafe {
            let self_ptr = self.get_ptr();
            assert!(!self_ptr.is_null());
            let input_ptr = input.get_ptr();
            assert!(!input_ptr.is_null());

            let matrix = match input_transform {
                Some(mat) => &mat.0 as *const _,
                None => ptr::null(),
            };

            let mut relation: D2D1_GEOMETRY_RELATION = D2D1_GEOMETRY_RELATION_UNKNOWN;
            let result = (*self_ptr).CompareWithGeometry(
                input_ptr,
                matrix,
                flattening_tolerance,
                &mut relation,
            );

            if SUCCEEDED(result) {
                use self::GeometryRelation::*;
                match relation {
                    D2D1_GEOMETRY_RELATION_UNKNOWN => Ok(Unknown),
                    D2D1_GEOMETRY_RELATION_DISJOINT => Ok(Disjoint),
                    D2D1_GEOMETRY_RELATION_IS_CONTAINED => Ok(IsContained),
                    D2D1_GEOMETRY_RELATION_CONTAINS => Ok(Contains),
                    D2D1_GEOMETRY_RELATION_OVERLAP => Ok(Overlap),
                    _ => Err(D2D1Error::UnknownEnumValue),
                }
            } else {
                Err(From::from(result))
            }
        }
    }

    fn compute_area(
        &self, world_transform: Option<&math::Matrix3x2F>, flattening_tolerance: f32
    ) -> Result<f32, D2D1Error> {
        unsafe {
            let ptr = self.get_ptr();
            assert!(!ptr.is_null());

            let matrix = match world_transform {
                Some(mat) => &mat.0 as *const _,
                None => ptr::null(),
            };

            let mut area = 0.0;
            let result = (*ptr).ComputeArea(
                matrix,
                flattening_tolerance,
                &mut area,
            );

            if SUCCEEDED(result) {
                Ok(area)
            } else {
                Err(From::from(result))
            }
        }
    }

    fn compute_length(
        &self, world_transform: Option<&math::Matrix3x2F>, flattening_tolerance: f32
    ) -> Result<f32, D2D1Error> {
        unsafe {
            let ptr = self.get_ptr();
            assert!(!ptr.is_null());

            let matrix = match world_transform {
                Some(mat) => &mat.0 as *const _,
                None => ptr::null(),
            };

            let mut length = 0.0;
            let result = (*ptr).ComputeLength(
                matrix,
                flattening_tolerance,
                &mut length,
            );

            if SUCCEEDED(result) {
                Ok(length)
            } else {
                Err(From::from(result))
            }
        }
    }
}

pub enum GeometryRelation {
    Unknown = 0,
    Disjoint = 1,
    IsContained = 2,
    Contains = 3,
    Overlap = 4,
}

/// Represents a rectangle which can be used anywhere Geometry is needed
pub struct Rectangle {
    geom: ComPtr<ID2D1RectangleGeometry>,
}

impl Rectangle {
    pub fn get_rect(&self) -> math::RectF {
        unsafe {
            let mut rect: D2D1_RECT_F = mem::uninitialized();
            (*self.geom.raw_value()).GetRect(&mut rect);
            math::RectF(rect)
        }
    }
}

impl Geometry for Rectangle {
    unsafe fn get_ptr(&self) -> *mut ID2D1Geometry {
        &mut **(&mut *self.geom.raw_value())
    }
}

/// Represents a rounded rectangle which can be used anywhere Geometry is needed
pub struct RoundedRectangle {
    geom: ComPtr<ID2D1RoundedRectangleGeometry>,
}

impl Geometry for RoundedRectangle {
    unsafe fn get_ptr(&self) -> *mut ID2D1Geometry {
        &mut **(&mut *self.geom.raw_value())
    }
}

<|endoftext|>