<commit_before>//! Interval-based annotation features.

use std::error::Error;
use std::collections::HashMap;
use std::fmt::{self, Display};

use bio::io::Strand;
use bio::utils::{Interval, IntervalError};

use self::error::FeatureError;

pub mod error {

    use super::*;

    #[derive(Debug, PartialEq)]
    pub enum FeatureError {
        IntervalError,
        StrandCharError,
        ConflictingStrandError,
        UnspecifiedStrandError,
    }

    impl Error for FeatureError {

        fn description(&self) -> &str {
            match *self {
                FeatureError::IntervalError => "interval start coordinate larger than its end coordinate",
                FeatureError::StrandCharError => "strand character is invalid",
                FeatureError::ConflictingStrandError => "conflicting strand inputs specified",
                FeatureError::UnspecifiedStrandError => "strand not specified",
            }
        }

        fn cause(&self) -> Option<&Error> {
            None
        }
    }

    impl Display for FeatureError {

        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
            write!(f, "FeatureError: {}", self.description())
        }
    }
}

trait Annotation {
    fn seq_name(&self) -> &str;
    fn interval(&self) -> &Interval<u64>;
    fn attributes(&self) -> &HashMap<String, String>;
    fn attribute(&self, key: &str) -> Option<&str>;
    fn strand(&self) -> &Strand;
}

macro_rules! impl_annotation {
    ($struct_ty:ty) => (

        impl Annotation for $struct_ty {

            fn seq_name(&self) -> &str {
                self.seq_name.as_str()
            }

            fn interval(&self) -> &Interval<u64> {
                &self.interval
            }

            fn attributes(&self) -> &HashMap<String, String> {
                &self.attributes
            }

            fn attribute(&self, key: &str) -> Option<&str> {
                self.attributes.get(key).map(|n| n.as_str())
            }

            fn strand(&self) -> &Strand {
                &self.strand
            }
        }

    );
}

fn char_to_strand(strand_char: char) -> Result<Strand, FeatureError> {
    match strand_char {
        '+' | 'f' | 'F' => Ok(Strand::Forward),
        '-' | 'r' | 'R' => Ok(Strand::Reverse),
        '.' | '?' => Ok(Strand::Unknown),
        _ => Err(FeatureError::StrandCharError),
    }
}

fn resolve_strand_input(strand: Option<Strand>, strand_char: Option<char>) -> Result<Strand, FeatureError> {
    match (strand, strand_char) {
        (None, None) => Err(FeatureError::UnspecifiedStrandError),
        (Some(sv), None) => Ok(sv),
        (None, Some(scv)) => char_to_strand(scv),
        (Some(sv), Some(scv)) => {
            let sv_from_char = char_to_strand(scv)?;
            if sv == sv_from_char {
                Ok(sv)
            } else {
                Err(FeatureError::ConflictingStrandError)
            }
        }
    }
}

fn coords_to_interval(start: u64, end: u64) -> Result<Interval<u64>, FeatureError> {
    Interval::new(start..end)
        .map_err(|err| match err {
            IntervalError::InvalidRange => FeatureError::IntervalError
        })
}

#[derive(Debug, Clone, Copy, PartialEq)]
pub enum TxFeature {
    Exon,
    UTR,
    UTR5,
    UTR3,
    CDS,
    StartCodon,
    StopCodon,
    Any,
}

#[derive(Debug)]
pub struct TranscriptFeature {
    kind: TxFeature,
    seq_name: String,
    interval: Interval<u64>,
    strand: Strand,
    attributes: HashMap<String, String>
}

impl_annotation!(TranscriptFeature);

impl TranscriptFeature {

    fn kind(&self) -> &TxFeature {
        &self.kind
    }
}

pub struct TxFeatureBuilder {
    seq_name: String,
    start: u64,
    end: u64,
    attributes: HashMap<String, String>,
    kind: TxFeature,
    strand: Option<Strand>,
    strand_char: Option<char>,
}

impl TxFeatureBuilder {

    fn new<T>(seq_name: T, start: u64, end: u64) -> TxFeatureBuilder
        where T: Into<String>
    {
        TxFeatureBuilder {
            seq_name: seq_name.into(),
            start: start, end: end,
            kind: TxFeature::Any,
            attributes: HashMap::new(),
            strand: None,
            strand_char: None,
        }
    }

    fn kind(mut self, kind: TxFeature) -> TxFeatureBuilder {
        self.kind = kind;
        self
    }

    fn strand(mut self, strand: Strand) -> TxFeatureBuilder {
        self.strand = Some(strand);
        self
    }

    fn strand_char(mut self, strand_char: char) -> TxFeatureBuilder {
        self.strand_char = Some(strand_char);
        self
    }

    fn attribute<K, V>(mut self, key: K, value: V) -> TxFeatureBuilder
        where K: Into<String>, V: Into<String>
    {
        self.attributes.insert(key.into(), value.into());
        self
    }

    fn build(self) -> Result<TranscriptFeature, FeatureError> {
        let interval = coords_to_interval(self.start, self.end)?;
        let strand = resolve_strand_input(self.strand, self.strand_char)?;
        let feature = TranscriptFeature {
            seq_name: self.seq_name, kind: self.kind, interval: interval,
            strand: strand, attributes: self.attributes,
        };
        Ok(feature)
    }
}

#[derive(Debug)]
pub struct Transcript {
    seq_name: String,
    interval: Interval<u64>,
    strand: Strand,
    attributes: HashMap<String, String>,
    features: Vec<TranscriptFeature>,
}

impl_annotation!(Transcript);

#[derive(Debug)]
pub struct Gene {
    seq_name: String,
    interval: Interval<u64>,
    strand: Strand,
    attributes: HashMap<String, String>,
    transcripts: HashMap<String, Transcript>,
}

impl_annotation!(Gene);

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

    #[test]
    fn test_builder() {
        let tfm1 = TxFeatureBuilder::new("chrT", 10, 20)
            .kind(TxFeature::Exon)
            .strand(Strand::Forward)
            .attribute("name", "ex1")
            .build();
        assert!(tfm1.is_ok());
        let tf = tfm1.unwrap();
        assert_eq!(tf.seq_name(), "chrT");
        assert_eq!(tf.kind(), &TxFeature::Exon);
        assert_eq!(tf.strand(), &Strand::Forward);
        assert_eq!(tf.attribute("name"), Some("ex1"));
        assert_eq!(tf.attributes.len(), 1);

        let tfm2 = TxFeatureBuilder::new("chrO", 10, 10)
            .strand_char('-')
            .strand(Strand::Reverse)
            .build();
        assert!(tfm2.is_ok());
    }

    #[test]
    fn test_builder_interval_invalid() {
        let tfm = TxFeatureBuilder::new("chrE", 20, 10).build();
        assert!(tfm.is_err());
        assert_eq!(tfm.unwrap_err(), FeatureError::IntervalError);
    }

    #[test]
    fn test_builder_strand_unspecified() {
        let tfm = TxFeatureBuilder::new("chrT", 20, 30)
            .build();
        assert!(tfm.is_err());
        assert_eq!(tfm.unwrap_err(), FeatureError::UnspecifiedStrandError);
    }

    #[test]
    fn test_build_strand_char_unexpected() {
        let tfm = TxFeatureBuilder::new("chrE", 10, 20)
            .strand_char('w')
            .build();
        assert!(tfm.is_err());
        assert_eq!(tfm.unwrap_err(), FeatureError::StrandCharError);
    }

    #[test]
    fn test_build_strand_char_conflicting() {
        let tfm = TxFeatureBuilder::new("chrE", 10, 20)
            .strand_char('-')
            .strand(Strand::Reverse)
            .build();
        assert!(tfm.is_ok());
        let tf = tfm.unwrap();
        assert_eq!(tf.strand(), &Strand::Reverse);
    }
}
<commit_msg>Update test names<commit_after>//! Interval-based annotation features.

use std::error::Error;
use std::collections::HashMap;
use std::fmt::{self, Display};

use bio::io::Strand;
use bio::utils::{Interval, IntervalError};

use self::error::FeatureError;

pub mod error {

    use super::*;

    #[derive(Debug, PartialEq)]
    pub enum FeatureError {
        IntervalError,
        StrandCharError,
        ConflictingStrandError,
        UnspecifiedStrandError,
    }

    impl Error for FeatureError {

        fn description(&self) -> &str {
            match *self {
                FeatureError::IntervalError => "interval start coordinate larger than its end coordinate",
                FeatureError::StrandCharError => "strand character is invalid",
                FeatureError::ConflictingStrandError => "conflicting strand inputs specified",
                FeatureError::UnspecifiedStrandError => "strand not specified",
            }
        }

        fn cause(&self) -> Option<&Error> {
            None
        }
    }

    impl Display for FeatureError {

        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
            write!(f, "FeatureError: {}", self.description())
        }
    }
}

trait Annotation {
    fn seq_name(&self) -> &str;
    fn interval(&self) -> &Interval<u64>;
    fn attributes(&self) -> &HashMap<String, String>;
    fn attribute(&self, key: &str) -> Option<&str>;
    fn strand(&self) -> &Strand;
}

macro_rules! impl_annotation {
    ($struct_ty:ty) => (

        impl Annotation for $struct_ty {

            fn seq_name(&self) -> &str {
                self.seq_name.as_str()
            }

            fn interval(&self) -> &Interval<u64> {
                &self.interval
            }

            fn attributes(&self) -> &HashMap<String, String> {
                &self.attributes
            }

            fn attribute(&self, key: &str) -> Option<&str> {
                self.attributes.get(key).map(|n| n.as_str())
            }

            fn strand(&self) -> &Strand {
                &self.strand
            }
        }

    );
}

fn char_to_strand(strand_char: char) -> Result<Strand, FeatureError> {
    match strand_char {
        '+' | 'f' | 'F' => Ok(Strand::Forward),
        '-' | 'r' | 'R' => Ok(Strand::Reverse),
        '.' | '?' => Ok(Strand::Unknown),
        _ => Err(FeatureError::StrandCharError),
    }
}

fn resolve_strand_input(strand: Option<Strand>, strand_char: Option<char>) -> Result<Strand, FeatureError> {
    match (strand, strand_char) {
        (None, None) => Err(FeatureError::UnspecifiedStrandError),
        (Some(sv), None) => Ok(sv),
        (None, Some(scv)) => char_to_strand(scv),
        (Some(sv), Some(scv)) => {
            let sv_from_char = char_to_strand(scv)?;
            if sv == sv_from_char {
                Ok(sv)
            } else {
                Err(FeatureError::ConflictingStrandError)
            }
        }
    }
}

fn coords_to_interval(start: u64, end: u64) -> Result<Interval<u64>, FeatureError> {
    Interval::new(start..end)
        .map_err(|err| match err {
            IntervalError::InvalidRange => FeatureError::IntervalError
        })
}

#[derive(Debug, Clone, Copy, PartialEq)]
pub enum TxFeature {
    Exon,
    UTR,
    UTR5,
    UTR3,
    CDS,
    StartCodon,
    StopCodon,
    Any,
}

#[derive(Debug)]
pub struct TranscriptFeature {
    kind: TxFeature,
    seq_name: String,
    interval: Interval<u64>,
    strand: Strand,
    attributes: HashMap<String, String>
}

impl_annotation!(TranscriptFeature);

impl TranscriptFeature {

    fn kind(&self) -> &TxFeature {
        &self.kind
    }
}

pub struct TxFeatureBuilder {
    seq_name: String,
    start: u64,
    end: u64,
    attributes: HashMap<String, String>,
    kind: TxFeature,
    strand: Option<Strand>,
    strand_char: Option<char>,
}

impl TxFeatureBuilder {

    fn new<T>(seq_name: T, start: u64, end: u64) -> TxFeatureBuilder
        where T: Into<String>
    {
        TxFeatureBuilder {
            seq_name: seq_name.into(),
            start: start, end: end,
            kind: TxFeature::Any,
            attributes: HashMap::new(),
            strand: None,
            strand_char: None,
        }
    }

    fn kind(mut self, kind: TxFeature) -> TxFeatureBuilder {
        self.kind = kind;
        self
    }

    fn strand(mut self, strand: Strand) -> TxFeatureBuilder {
        self.strand = Some(strand);
        self
    }

    fn strand_char(mut self, strand_char: char) -> TxFeatureBuilder {
        self.strand_char = Some(strand_char);
        self
    }

    fn attribute<K, V>(mut self, key: K, value: V) -> TxFeatureBuilder
        where K: Into<String>, V: Into<String>
    {
        self.attributes.insert(key.into(), value.into());
        self
    }

    fn build(self) -> Result<TranscriptFeature, FeatureError> {
        let interval = coords_to_interval(self.start, self.end)?;
        let strand = resolve_strand_input(self.strand, self.strand_char)?;
        let feature = TranscriptFeature {
            seq_name: self.seq_name, kind: self.kind, interval: interval,
            strand: strand, attributes: self.attributes,
        };
        Ok(feature)
    }
}

#[derive(Debug)]
pub struct Transcript {
    seq_name: String,
    interval: Interval<u64>,
    strand: Strand,
    attributes: HashMap<String, String>,
    features: Vec<TranscriptFeature>,
}

impl_annotation!(Transcript);

#[derive(Debug)]
pub struct Gene {
    seq_name: String,
    interval: Interval<u64>,
    strand: Strand,
    attributes: HashMap<String, String>,
    transcripts: HashMap<String, Transcript>,
}

impl_annotation!(Gene);

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

    #[test]
    fn builder() {
        let tfm1 = TxFeatureBuilder::new("chrT", 10, 20)
            .kind(TxFeature::Exon)
            .strand(Strand::Forward)
            .attribute("name", "ex1")
            .build();
        assert!(tfm1.is_ok());
        let tf = tfm1.unwrap();
        assert_eq!(tf.seq_name(), "chrT");
        assert_eq!(tf.kind(), &TxFeature::Exon);
        assert_eq!(tf.strand(), &Strand::Forward);
        assert_eq!(tf.attribute("name"), Some("ex1"));
        assert_eq!(tf.attributes.len(), 1);

        let tfm2 = TxFeatureBuilder::new("chrO", 10, 10)
            .strand_char('-')
            .strand(Strand::Reverse)
            .build();
        assert!(tfm2.is_ok());
    }

    #[test]
    fn builder_interval_invalid() {
        let tfm = TxFeatureBuilder::new("chrE", 20, 10).build();
        assert!(tfm.is_err());
        assert_eq!(tfm.unwrap_err(), FeatureError::IntervalError);
    }

    #[test]
    fn builder_strand_unspecified() {
        let tfm = TxFeatureBuilder::new("chrT", 20, 30)
            .build();
        assert!(tfm.is_err());
        assert_eq!(tfm.unwrap_err(), FeatureError::UnspecifiedStrandError);
    }

    #[test]
    fn builder_strand_char_unexpected() {
        let tfm = TxFeatureBuilder::new("chrE", 10, 20)
            .strand_char('w')
            .build();
        assert!(tfm.is_err());
        assert_eq!(tfm.unwrap_err(), FeatureError::StrandCharError);
    }

    #[test]
    fn builder_strand_char_conflicting() {
        let tfm = TxFeatureBuilder::new("chrE", 10, 20)
            .strand_char('-')
            .strand(Strand::Reverse)
            .build();
        assert!(tfm.is_ok());
        let tf = tfm.unwrap();
        assert_eq!(tf.strand(), &Strand::Reverse);
    }
}
<|endoftext|>


<commit_before>//! Interval-based annotation features.

use std::collections::HashMap;
use std::iter::FromIterator;

use bio::data_structures::interval_tree::{IntervalTree, IntervalTreeIterator};
use bio::io::Strand;
use bio::utils::{Interval, IntervalError};

pub trait NamedInterval: Sized {

    /// Underlying interval struct.
    fn interval(&self) -> &Interval<u64>;  // TODO: Generalize over interval types.

    /// Name of the interval.
    fn name(&self) -> Option<&str>;

    /// Name setter that returns the implementor itself.
    ///
    /// This function is expected to mutate the implementing type.
    fn with_name<T: Into<String>>(self, name: T) -> Self;

    /// Coordinate setter that returns the implementor itself.
    ///
    /// This function is expected to mutate the implementing type.
    fn with_coords(self, start: u64, end: u64) -> Result<Self, IntervalError>;

    /// Start coordinate of the interval.
    fn start(&self) -> u64 {
        self.interval().start
    }

    /// End coordinate of the interval.
    fn end(&self) -> u64 {
        self.interval().end
    }

    /// The number of bases covered by the interval.
    fn span(&self) -> u64 {
        self.end() - self.start()
    }

    /// Whether two intervals have an overlap or not.
    fn overlaps(&self, other: &Self) -> bool {
        self.start() < other.end() && other.start() < self.end()
    }

    /// Whether one interval completely contains the other.
    fn envelops(&self, other: &Self) -> bool {
        self.start() <= other.start() && self.end() >= other.end()
    }

    /// Whether two intervals cover a contiguous region without any overlaps.
    fn is_adjacent(&self, other: &Self) -> bool {
        self.end() == other.start() || self.start() == other.end()
    }
}

/// Macro for default function implementations of interval types.
macro_rules! impl_ninterval {
    ($struct_ty:ty) => (

        impl NamedInterval for $struct_ty {

            /// Name of the interval.
            fn name(&self) -> Option<&str> {
                self.name.as_ref().map(|n| n.as_str())
            }

            fn with_name<T>(mut self, name: T) -> $struct_ty
                where T: Into<String>
            {
                self.name = Some(name.into());
                self
            }

            fn interval(&self) -> &Interval<u64> {
                &self.interval
            }

            fn with_coords(mut self, start: u64, end: u64) -> Result<$struct_ty, IntervalError> {
                Interval::new(start..end)
                    .map(|iv| {
                        self.interval = iv;
                        self
                    })
            }
        }

    );
}

/// Default implementation of the `Interval` trait.
///
/// This struct also provides static methods for creating exons, transcripts, and genes.
#[derive(Debug)]
pub struct Feature {
    interval: Interval<u64>,
    name: Option<String>,
}

impl Default for Feature {

    fn default() -> Feature {
        Feature { interval: Interval::new(0..0).unwrap(), name: None }
    }
}

impl Feature {

    /// Creates a gene interval with default values.
    ///
    /// A gene interval is a container for transcript intervals.
    ///
    /// # Examples
    ///
    /// ```
    /// let gene = Feature::gene();
    ///
    /// assert_eq!(gene.transcript().len(), 0);
    /// assert_eq!(gene.start(), 0);
    /// assert_eq!(gene.end(), 0);
    /// assert_eq!(gene.name(), None);
    /// ```
    pub fn gene() -> Gene {
        Gene::default()
    }

    /// Creates a transcript interval with default values.
    ///
    /// A transcript interval is a container for exon intervals.
    ///
    /// # Examples
    ///
    /// ```
    /// use bio::io::Strand;
    ///
    /// let transcript = Feature::transcript();
    ///
    /// assert_eq!(transcript.exons().len(), 0);
    /// assert_eq!(transcript.strand(), &Strand::Unknown)
    /// assert_eq!(transcript.start(), 0);
    /// assert_eq!(transcript.end(), 0);
    /// assert_eq!(transcript.name(), None);
    /// ```
    pub fn transcript() -> Transcript {
        Transcript::default()
    }

    /// Creates an exon interval with default values.
    ///
    /// # Examples
    ///
    /// ```
    /// let exon = Feature::exon();
    ///
    /// assert_eq!(exon.start(), 0);
    /// assert_eq!(exon.end(), 0);
    /// assert_eq!(exon.name(), None);
    /// ```
    pub fn exon() -> Exon {
        Exon::default()
    }
}

impl_ninterval!(Feature);

/// Exon annotation.
#[derive(Debug)]
pub struct Exon {
    interval: Interval<u64>,
    name: Option<String>,
}

impl Default for Exon {

    fn default() -> Exon {
        Exon { interval: Interval::new(0..0).unwrap(), name: None }
    }
}

impl_ninterval!(Exon);

/// Transcript annotation.
#[derive(Debug)]
pub struct Transcript {
    interval: Interval<u64>,
    name: Option<String>,
    strand: Strand,
    cds_start: Option<u64>,
    cds_end: Option<u64>,
    exons: IntervalTree<u64, Exon>,
}

impl Default for Transcript {

    fn default() -> Transcript {
        Transcript {
            interval: Interval::new(0..0).unwrap(),
            name: None,
            strand: Strand::Unknown,
            cds_start: None,
            cds_end: None,
            exons: IntervalTree::new(),
        }
    }
}

impl_ninterval!(Transcript);

impl Transcript {

    pub fn strand(&self) -> &Strand {
        &self.strand
    }

    pub fn with_strand(mut self, strand: Strand) -> Transcript {
        self.strand = strand;
        self
    }

    pub fn exons(&self) -> &IntervalTree<u64, Exon> {
        &self.exons
    }

    pub fn with_exons<I>(mut self, exons: I) -> Transcript
        where I: IntoIterator<Item=Exon>
    {
        let exp_iter = exons.into_iter()
            .map(|exn| (exn.interval.clone(), exn));
        self.exons = IntervalTree::from_iter(exp_iter);
        self
    }

    pub fn insert_exon(&mut self, exon: Exon) {
        self.exons.insert(exon.start()..exon.end(), exon);
    }

    pub fn cds_start(&self) -> Option<u64> {
        self.cds_start
    }

    pub fn with_cds_start(mut self, cds_start: u64) -> Transcript {
        self.cds_start = Some(cds_start);
        self
    }

    pub fn cds_end(&self) -> Option<u64> {
        self.cds_end
    }

    pub fn with_cds_end(mut self, cds_end: u64) -> Transcript {
        self.cds_end = Some(cds_end);
        self
    }

    pub fn iter(&self) -> IntervalTreeIterator<u64, Exon> {
        // FIXME: iteration over IntervalTree items instead of using (min, max) hack.
        self.exons.find(0..u64::max_value())
    }

    /// Returns the number of exons in the trancripts in O(n) time.
    pub fn len(&self) -> usize {
        self.iter().map(|_exon| 1).fold(0, |acc, x| acc + x)
    }
}

/// Gene annotation.
#[derive(Debug)]
pub struct Gene {
    interval: Interval<u64>,
    name: Option<String>,
    transcripts: HashMap<String, Transcript>,
}

impl Default for Gene {

    fn default() -> Gene {
        Gene { interval: Interval::new(0..0).unwrap(),
               name: None,
               transcripts: HashMap::new(),
        }
    }
}

impl_ninterval!(Gene);

impl Gene {

    pub fn transcripts(&self) -> &HashMap<String, Transcript> {
        &self.transcripts
    }
}

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

    #[test]
    fn default() {
        let fx = Feature::default();
        assert_eq!(fx.start(), 0);
        assert_eq!(fx.end(), 0);
        assert_eq!(fx.name(), None);
    }

    #[test]
    fn with_name() {
        let fx1 = Feature::default()
            .with_name("fx1");
        assert_eq!(fx1.start(), 0);
        assert_eq!(fx1.end(), 0);
        assert_eq!(fx1.name(), Some("fx1"));

        let fx2 = Feature::default()
            .with_name("fx2".to_owned());
        assert_eq!(fx2.start(), 0);
        assert_eq!(fx2.end(), 0);
        assert_eq!(fx2.name(), Some("fx2"));
    }

    #[test]
    fn with_coords() {
        let fxm = Feature::default()
            .with_coords(1, 3);
        assert!(fxm.is_ok());
        let fx = fxm.unwrap();
        assert_eq!(fx.start(), 1);
        assert_eq!(fx.end(), 3);
        assert_eq!(fx.name(), None);
    }

    #[test]
    fn with_coords_err() {
        let fxm = Feature::default()
            .with_coords(3, 1);
        assert!(fxm.is_err());
    }

    #[test]
    fn with_multiples() {
        let fxm = Feature::default()
            .with_coords(20, 30)
            .map(|f| f.with_name("fx"));
        assert!(fxm.is_ok());
        let fx = fxm.unwrap();
        assert_eq!(fx.start(), 20);
        assert_eq!(fx.end(), 30);
        assert_eq!(fx.name(), Some("fx"));
    }

    fn make_feature(start: u64, end: u64) -> Feature {
        Feature::default().with_coords(start, end).unwrap()
    }

    #[test]
    fn span() {
        let fx = make_feature(0, 15);
        assert_eq!(fx.span(), 15);
    }

    #[test]
    fn overlaps() {
        let fx1 = make_feature(100, 115);

        let fx2 = make_feature(110, 120);
        assert!(fx1.overlaps(&fx2));
        assert!(fx1.overlaps(&fx2));

        let fx3 = make_feature(115, 120);
        assert!(!fx1.overlaps(&fx3));
        assert!(!fx3.overlaps(&fx1));

        let fx4 = make_feature(90, 100);
        assert!(!fx1.overlaps(&fx4));
        assert!(!fx4.overlaps(&fx1));

        let fx5 = make_feature(200, 300);
        assert!(!fx1.overlaps(&fx5));
        assert!(!fx5.overlaps(&fx1));
    }

    #[test]
    fn envelops() {
        let fx1 = make_feature(100, 120);

        let fx2 = make_feature(105, 115);
        assert!(fx1.envelops(&fx2));
        assert!(!fx2.envelops(&fx1));

        let fx3 = make_feature(100, 105);
        assert!(fx1.envelops(&fx3));
        assert!(!fx3.envelops(&fx1));

        let fx4 = make_feature(115, 120);
        assert!(fx1.envelops(&fx4));
        assert!(!fx4.envelops(&fx1));

        let fx5 = make_feature(90, 105);
        assert!(!fx1.envelops(&fx5));
        assert!(!fx5.envelops(&fx1));

        let fx6 = make_feature(115, 130);
        assert!(!fx1.envelops(&fx5));
        assert!(!fx6.envelops(&fx1));

        let fx7 = make_feature(80, 90);
        assert!(!fx1.envelops(&fx7));
        assert!(!fx7.envelops(&fx1));
    }

    #[test]
    fn is_adjacent() {
        let fx1 = make_feature(100, 120);

        let fx2 = make_feature(90, 100);
        assert!(fx1.is_adjacent(&fx2));
        assert!(fx2.is_adjacent(&fx1));

        let fx3 = make_feature(120, 130);
        assert!(fx1.is_adjacent(&fx3));
        assert!(fx3.is_adjacent(&fx1));

        let fx4 = make_feature(90, 99);
        assert!(!fx1.is_adjacent(&fx4));
        assert!(!fx4.is_adjacent(&fx1));

        let fx5 = make_feature(119, 130);
        assert!(!fx1.is_adjacent(&fx5));
        assert!(!fx5.is_adjacent(&fx1));

        let fx6 = make_feature(100, 110);
        assert!(!fx1.is_adjacent(&fx6));
        assert!(!fx6.is_adjacent(&fx1));

        let fx7 = make_feature(110, 120);
        assert!(!fx1.is_adjacent(&fx7));
        assert!(!fx7.is_adjacent(&fx1));
    }
}

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

    #[test]
    fn default() {
        let exon = Feature::exon();
        assert_eq!(exon.start(), 0);
        assert_eq!(exon.end(), 0);
        assert_eq!(exon.name(), None);
    }
}

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

    #[test]
    fn default() {
        let trx = Feature::transcript();
        assert_eq!(trx.start(), 0);
        assert_eq!(trx.end(), 0);
        assert_eq!(trx.name(), None);
        assert_eq!(trx.strand(), &Strand::Unknown);
        assert_eq!(trx.cds_start(), None);
        assert_eq!(trx.cds_end(), None);
        assert_eq!(trx.len(), 0);
    }

    #[test]
    fn with_strand() {
        let trx = Feature::transcript()
            .with_strand(Strand::Forward);
        assert_eq!(trx.strand(), &Strand::Forward);
    }

    #[test]
    fn with_cds_start() {
        let trx = Feature::transcript()
            .with_cds_start(20);
        assert_eq!(trx.cds_start(), Some(20));
    }

    #[test]
    fn with_cds_end() {
        let trx = Feature::transcript()
            .with_cds_end(40);
        assert_eq!(trx.cds_end(), Some(40))
    }

    fn make_exon<T: Into<String>>(start: u64, end: u64, name: T) -> Exon {
        Feature::exon()
            .with_name(name)
            .with_coords(start, end).unwrap()
    }

    #[test]
    fn with_exons() {
        let trx = Feature::transcript()
            .with_exons(vec![
                make_exon(1, 2, "ex1"),
                make_exon(10, 20, "ex2"),
                make_exon(100, 200, "ex3"),
            ]);
        assert_eq!(trx.len(), 3);
    }

    #[test]
    fn insert_exon() {
        let mut trx = Feature::transcript();
        assert_eq!(trx.len(), 0);
        trx.insert_exon(make_exon(1, 2, "ex"));
        assert_eq!(trx.len(), 1);
    }
}

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

    #[test]
    fn default() {
        let gene = Feature::gene();
        assert_eq!(gene.start(), 0);
        assert_eq!(gene.end(), 0);
        assert_eq!(gene.name(), None);
        assert_eq!(gene.transcripts().len(), 0);
    }
}
<commit_msg>Use Feature for CDS<commit_after>//! Interval-based annotation features.

use std::collections::HashMap;
use std::iter::FromIterator;

use bio::data_structures::interval_tree::{IntervalTree, IntervalTreeIterator};
use bio::io::Strand;
use bio::utils::{Interval, IntervalError};

pub trait NamedInterval: Sized {

    /// Underlying interval struct.
    fn interval(&self) -> &Interval<u64>;  // TODO: Generalize over interval types.

    /// Name of the interval.
    fn name(&self) -> Option<&str>;

    /// Name setter that returns the implementor itself.
    ///
    /// This function is expected to mutate the implementing type.
    fn with_name<T: Into<String>>(self, name: T) -> Self;

    /// Coordinate setter that returns the implementor itself.
    ///
    /// This function is expected to mutate the implementing type.
    fn with_coords(self, start: u64, end: u64) -> Result<Self, IntervalError>;

    /// Start coordinate of the interval.
    fn start(&self) -> u64 {
        self.interval().start
    }

    /// End coordinate of the interval.
    fn end(&self) -> u64 {
        self.interval().end
    }

    /// The number of bases covered by the interval.
    fn span(&self) -> u64 {
        self.end() - self.start()
    }

    /// Whether two intervals have an overlap or not.
    fn overlaps(&self, other: &Self) -> bool {
        self.start() < other.end() && other.start() < self.end()
    }

    /// Whether one interval completely contains the other.
    fn envelops(&self, other: &Self) -> bool {
        self.start() <= other.start() && self.end() >= other.end()
    }

    /// Whether two intervals cover a contiguous region without any overlaps.
    fn is_adjacent(&self, other: &Self) -> bool {
        self.end() == other.start() || self.start() == other.end()
    }
}

/// Macro for default function implementations of interval types.
macro_rules! impl_ninterval {
    ($struct_ty:ty) => (

        impl NamedInterval for $struct_ty {

            /// Name of the interval.
            fn name(&self) -> Option<&str> {
                self.name.as_ref().map(|n| n.as_str())
            }

            fn with_name<T>(mut self, name: T) -> $struct_ty
                where T: Into<String>
            {
                self.name = Some(name.into());
                self
            }

            fn interval(&self) -> &Interval<u64> {
                &self.interval
            }

            fn with_coords(mut self, start: u64, end: u64) -> Result<$struct_ty, IntervalError> {
                Interval::new(start..end)
                    .map(|iv| {
                        self.interval = iv;
                        self
                    })
            }
        }

    );
}

/// Default implementation of the `Interval` trait.
///
/// This struct also provides static methods for creating exons, transcripts, and genes.
#[derive(Debug)]
pub struct Feature {
    interval: Interval<u64>,
    name: Option<String>,
}

impl Default for Feature {

    fn default() -> Feature {
        Feature { interval: Interval::new(0..0).unwrap(), name: None }
    }
}

impl Feature {

    /// Creates a gene interval with default values.
    ///
    /// A gene interval is a container for transcript intervals.
    ///
    /// # Examples
    ///
    /// ```
    /// let gene = Feature::gene();
    ///
    /// assert_eq!(gene.transcript().len(), 0);
    /// assert_eq!(gene.start(), 0);
    /// assert_eq!(gene.end(), 0);
    /// assert_eq!(gene.name(), None);
    /// ```
    pub fn gene() -> Gene {
        Gene::default()
    }

    /// Creates a transcript interval with default values.
    ///
    /// A transcript interval is a container for exon intervals.
    ///
    /// # Examples
    ///
    /// ```
    /// use bio::io::Strand;
    ///
    /// let transcript = Feature::transcript();
    ///
    /// assert_eq!(transcript.exons().len(), 0);
    /// assert_eq!(transcript.strand(), &Strand::Unknown)
    /// assert_eq!(transcript.start(), 0);
    /// assert_eq!(transcript.end(), 0);
    /// assert_eq!(transcript.name(), None);
    /// ```
    pub fn transcript() -> Transcript {
        Transcript::default()
    }

    /// Creates an exon interval with default values.
    ///
    /// # Examples
    ///
    /// ```
    /// let exon = Feature::exon();
    ///
    /// assert_eq!(exon.start(), 0);
    /// assert_eq!(exon.end(), 0);
    /// assert_eq!(exon.name(), None);
    /// ```
    pub fn exon() -> Exon {
        Exon::default()
    }
}

impl_ninterval!(Feature);

/// Exon annotation.
#[derive(Debug)]
pub struct Exon {
    interval: Interval<u64>,
    name: Option<String>,
}

impl Default for Exon {

    fn default() -> Exon {
        Exon { interval: Interval::new(0..0).unwrap(), name: None }
    }
}

impl_ninterval!(Exon);

/// Transcript annotation.
#[derive(Debug)]
pub struct Transcript {
    interval: Interval<u64>,
    name: Option<String>,
    strand: Strand,
    cds: Option<Feature>,
    exons: IntervalTree<u64, Exon>,
}

impl Default for Transcript {

    fn default() -> Transcript {
        Transcript {
            interval: Interval::new(0..0).unwrap(),
            name: None,
            strand: Strand::Unknown,
            cds: None,
            exons: IntervalTree::new(),
        }
    }
}

impl_ninterval!(Transcript);

impl Transcript {

    pub fn strand(&self) -> &Strand {
        &self.strand
    }

    pub fn with_strand(mut self, strand: Strand) -> Transcript {
        self.strand = strand;
        self
    }

    pub fn exons(&self) -> &IntervalTree<u64, Exon> {
        &self.exons
    }

    pub fn with_exons<I>(mut self, exons: I) -> Transcript
        where I: IntoIterator<Item=Exon>
    {
        let exp_iter = exons.into_iter()
            .map(|exn| (exn.interval.clone(), exn));
        self.exons = IntervalTree::from_iter(exp_iter);
        self
    }

    pub fn insert_exon(&mut self, exon: Exon) {
        self.exons.insert(exon.start()..exon.end(), exon);
    }

    pub fn cds(&self) -> Option<&Feature> {
        self.cds.as_ref()
    }

    pub fn with_cds(mut self, cds: Feature) -> Transcript {
        self.cds = Some(cds);
        self
    }

    pub fn iter(&self) -> IntervalTreeIterator<u64, Exon> {
        // FIXME: iteration over IntervalTree items instead of using (min, max) hack.
        self.exons.find(0..u64::max_value())
    }

    /// Returns the number of exons in the trancripts in O(n) time.
    pub fn len(&self) -> usize {
        self.iter().map(|_exon| 1).fold(0, |acc, x| acc + x)
    }
}

/// Gene annotation.
#[derive(Debug)]
pub struct Gene {
    interval: Interval<u64>,
    name: Option<String>,
    transcripts: HashMap<String, Transcript>,
}

impl Default for Gene {

    fn default() -> Gene {
        Gene { interval: Interval::new(0..0).unwrap(),
               name: None,
               transcripts: HashMap::new(),
        }
    }
}

impl_ninterval!(Gene);

impl Gene {

    pub fn transcripts(&self) -> &HashMap<String, Transcript> {
        &self.transcripts
    }
}

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

    #[test]
    fn default() {
        let fx = Feature::default();
        assert_eq!(fx.start(), 0);
        assert_eq!(fx.end(), 0);
        assert_eq!(fx.name(), None);
    }

    #[test]
    fn with_name() {
        let fx1 = Feature::default()
            .with_name("fx1");
        assert_eq!(fx1.start(), 0);
        assert_eq!(fx1.end(), 0);
        assert_eq!(fx1.name(), Some("fx1"));

        let fx2 = Feature::default()
            .with_name("fx2".to_owned());
        assert_eq!(fx2.start(), 0);
        assert_eq!(fx2.end(), 0);
        assert_eq!(fx2.name(), Some("fx2"));
    }

    #[test]
    fn with_coords() {
        let fxm = Feature::default()
            .with_coords(1, 3);
        assert!(fxm.is_ok());
        let fx = fxm.unwrap();
        assert_eq!(fx.start(), 1);
        assert_eq!(fx.end(), 3);
        assert_eq!(fx.name(), None);
    }

    #[test]
    fn with_coords_err() {
        let fxm = Feature::default()
            .with_coords(3, 1);
        assert!(fxm.is_err());
    }

    #[test]
    fn with_multiples() {
        let fxm = Feature::default()
            .with_coords(20, 30)
            .map(|f| f.with_name("fx"));
        assert!(fxm.is_ok());
        let fx = fxm.unwrap();
        assert_eq!(fx.start(), 20);
        assert_eq!(fx.end(), 30);
        assert_eq!(fx.name(), Some("fx"));
    }

    fn make_feature(start: u64, end: u64) -> Feature {
        Feature::default().with_coords(start, end).unwrap()
    }

    #[test]
    fn span() {
        let fx = make_feature(0, 15);
        assert_eq!(fx.span(), 15);
    }

    #[test]
    fn overlaps() {
        let fx1 = make_feature(100, 115);

        let fx2 = make_feature(110, 120);
        assert!(fx1.overlaps(&fx2));
        assert!(fx1.overlaps(&fx2));

        let fx3 = make_feature(115, 120);
        assert!(!fx1.overlaps(&fx3));
        assert!(!fx3.overlaps(&fx1));

        let fx4 = make_feature(90, 100);
        assert!(!fx1.overlaps(&fx4));
        assert!(!fx4.overlaps(&fx1));

        let fx5 = make_feature(200, 300);
        assert!(!fx1.overlaps(&fx5));
        assert!(!fx5.overlaps(&fx1));
    }

    #[test]
    fn envelops() {
        let fx1 = make_feature(100, 120);

        let fx2 = make_feature(105, 115);
        assert!(fx1.envelops(&fx2));
        assert!(!fx2.envelops(&fx1));

        let fx3 = make_feature(100, 105);
        assert!(fx1.envelops(&fx3));
        assert!(!fx3.envelops(&fx1));

        let fx4 = make_feature(115, 120);
        assert!(fx1.envelops(&fx4));
        assert!(!fx4.envelops(&fx1));

        let fx5 = make_feature(90, 105);
        assert!(!fx1.envelops(&fx5));
        assert!(!fx5.envelops(&fx1));

        let fx6 = make_feature(115, 130);
        assert!(!fx1.envelops(&fx5));
        assert!(!fx6.envelops(&fx1));

        let fx7 = make_feature(80, 90);
        assert!(!fx1.envelops(&fx7));
        assert!(!fx7.envelops(&fx1));
    }

    #[test]
    fn is_adjacent() {
        let fx1 = make_feature(100, 120);

        let fx2 = make_feature(90, 100);
        assert!(fx1.is_adjacent(&fx2));
        assert!(fx2.is_adjacent(&fx1));

        let fx3 = make_feature(120, 130);
        assert!(fx1.is_adjacent(&fx3));
        assert!(fx3.is_adjacent(&fx1));

        let fx4 = make_feature(90, 99);
        assert!(!fx1.is_adjacent(&fx4));
        assert!(!fx4.is_adjacent(&fx1));

        let fx5 = make_feature(119, 130);
        assert!(!fx1.is_adjacent(&fx5));
        assert!(!fx5.is_adjacent(&fx1));

        let fx6 = make_feature(100, 110);
        assert!(!fx1.is_adjacent(&fx6));
        assert!(!fx6.is_adjacent(&fx1));

        let fx7 = make_feature(110, 120);
        assert!(!fx1.is_adjacent(&fx7));
        assert!(!fx7.is_adjacent(&fx1));
    }
}

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

    #[test]
    fn default() {
        let exon = Feature::exon();
        assert_eq!(exon.start(), 0);
        assert_eq!(exon.end(), 0);
        assert_eq!(exon.name(), None);
    }
}

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

    #[test]
    fn default() {
        let trx = Feature::transcript();
        assert_eq!(trx.start(), 0);
        assert_eq!(trx.end(), 0);
        assert_eq!(trx.name(), None);
        assert_eq!(trx.strand(), &Strand::Unknown);
        assert!(trx.cds().is_none());
        assert_eq!(trx.len(), 0);
    }

    #[test]
    fn with_strand() {
        let trx = Feature::transcript()
            .with_strand(Strand::Forward);
        assert_eq!(trx.strand(), &Strand::Forward);
    }

    fn make_exon<T: Into<String>>(start: u64, end: u64, name: T) -> Exon {
        Feature::exon()
            .with_name(name)
            .with_coords(start, end).unwrap()
    }

    #[test]
    fn with_exons() {
        let trx = Feature::transcript()
            .with_exons(vec![
                make_exon(1, 2, "ex1"),
                make_exon(10, 20, "ex2"),
                make_exon(100, 200, "ex3"),
            ]);
        assert_eq!(trx.len(), 3);
    }

    #[test]
    fn insert_exon() {
        let mut trx = Feature::transcript();
        assert_eq!(trx.len(), 0);
        trx.insert_exon(make_exon(1, 2, "ex"));
        assert_eq!(trx.len(), 1);
    }
}

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

    #[test]
    fn default() {
        let gene = Feature::gene();
        assert_eq!(gene.start(), 0);
        assert_eq!(gene.end(), 0);
        assert_eq!(gene.name(), None);
        assert_eq!(gene.transcripts().len(), 0);
    }
}
<|endoftext|>


<commit_before>#[phase(plugin)]
extern crate regex_macros;
extern crate regex;
extern crate serialize;
#[phase(plugin, link)] extern crate log;

pub use errors;

use std::io::process::Command;
use utils::say::{sayln, spinner};
use errors::{DeliveryError, Kind};

pub fn get_head() -> Result<String, DeliveryError> {
    let gitr = try!(git_command(&["branch"]));
    let result = try!(parse_get_head(gitr.stdout.as_slice()));
    Ok(result)
}

fn parse_get_head(stdout: &str) -> Result<String, DeliveryError> {
    for line in stdout.lines_any() {
        let r = regex!(r"(.) (.+)");
        let caps_result = r.captures(line);
        let caps = match caps_result {
            Some(caps) => caps,
            None => { return Err(DeliveryError{ kind: Kind::BadGitOutputMatch, detail: Some(format!("Failed to match: {}", line)) }) }
        };
        if caps.at(1) == "*" {
            return Ok(String::from_str(caps.at(2)));
        }
    }
    return Err(DeliveryError{ kind: Kind::NotOnABranch, detail: None });
}

#[test]
fn test_parse_get_head() {
    let stdout = "  adam/review
  adam/test
  adam/test6
  builder
  first
  foo
  foo2
* master
  snazzy
  testerton";
    let result = parse_get_head(stdout);
    match result {
        Ok(branch) => {
            assert_eq!(branch.as_slice(), "master");
        },
        Err(e) => panic!("No result")
    };
}

pub struct GitResult {
    stdout: String,
    stderr: String
}

fn git_command(args: &[&str]) -> Result<GitResult, DeliveryError> {
    let (tx, rx) = channel::<int>();
    let (tx2, rx2) = channel::<int>();
    spawn(proc() spinner(rx, tx2));
    let mut command = Command::new("git");
    command.args(args);
    debug!("Git command: {}", command);
    let output = match command.output() {
        Ok(o) => o,
        Err(e) => { tx.send(1); return Err(DeliveryError{ kind: Kind::FailedToExecute, detail: Some(format!("failed to execute git: {}", e.desc))}) },
    };
    debug!("Git exited: {}", output.status);
    tx.send(1);
    rx2.recv();
    if !output.status.success() {
        return Err(DeliveryError{ kind: Kind::GitFailed, detail: Some(format!("STDOUT: {}\nSTDERR: {}\n", String::from_utf8_lossy(output.output.as_slice()), String::from_utf8_lossy(output.error.as_slice())))});
    }
    let stdout = String::from_utf8_lossy(output.output.as_slice()).into_string();
    debug!("Git stdout: {}", stdout);
    let stderr = String::from_utf8_lossy(output.error.as_slice()).into_string();
    debug!("Git stderr: {}", stderr);
    Ok(GitResult{ stdout: stdout, stderr: stderr })
}

pub fn git_push(branch: &str, target: &str) -> Result<String, DeliveryError> {
    let gitr = try!(git_command(&[
                     "push", "--porcelain", "--progress", "--verbose", "delivery", format!("{}:_for/{}/{}", branch, target, branch).as_slice()
                     ]));
    let output = try!(parse_git_push_output(gitr.stdout.as_slice(), gitr.stderr.as_slice()));
    for result in output.iter() {
        match result.flag {
            PushResultFlags::SuccessfulFastForward => sayln("green", format!("Updated change: {}", result.reason).as_slice()),
            PushResultFlags::SuccessfulForcedUpdate => sayln("green", format!("Force updated change: {}", result.reason).as_slice()),
            PushResultFlags::SuccessfulDeletedRef => sayln("red", format!("Deleted change: {}", result.reason).as_slice()),
            PushResultFlags::SuccessfulPushedNewRef => sayln("green", format!("Created change: {}", result.reason).as_slice()),
            PushResultFlags::Rejected => sayln("red", format!("Rejected change: {}", result.reason).as_slice()),
            PushResultFlags::UpToDate => sayln("yellow", format!("Nothing added to the existing change").as_slice()),
        }
    }
    Ok(gitr.stdout.into_string())
}

pub enum PushResultFlags {
    SuccessfulFastForward,
    SuccessfulForcedUpdate,
    SuccessfulDeletedRef,
    SuccessfulPushedNewRef,
    Rejected,
    UpToDate,
}

pub struct PushResult {
    flag: PushResultFlags,
    from: String,
    to: String,
    reason: String
}

pub fn parse_git_push_output(push_output: &str, push_error: &str) -> Result<Vec<PushResult>, DeliveryError> {
    let mut push_results: Vec<PushResult> = Vec::new();
    for line in push_error.lines_any() {
        debug!("error: {}", line);
        if line.starts_with("remote") {
            let r = regex!(r"remote: (.+)");
            let caps_result = r.captures(line);
            match caps_result {
                Some(caps) => sayln("white", format!("{}", caps.at(1)).as_slice()),
                None => {}
            }
        }
    }
    for line in push_output.lines_any() {
        debug!("output: {}", line);
        if line.starts_with("To") {
            continue;
        } else if line.starts_with("Done") {
            continue;
        }
        let r = regex!(r"(.)\t(.+):(.+)\t\[(.+)\]");
        let caps_result = r.captures(line);
        let caps = match caps_result {
            Some(caps) => caps,
            None => { return Err(DeliveryError{ kind: Kind::BadGitOutputMatch, detail: Some(format!("Failed to match: {}", line)) }) }
        };
        let result_flag = match caps.at(1) {
            " " => PushResultFlags::SuccessfulFastForward,
            "+" => PushResultFlags::SuccessfulForcedUpdate,
            "-" => PushResultFlags::SuccessfulDeletedRef,
            "*" => PushResultFlags::SuccessfulPushedNewRef,
            "!" => PushResultFlags::Rejected,
            "=" => PushResultFlags::UpToDate,
            _ => { return Err(DeliveryError{ kind: Kind::BadGitOutputMatch, detail: Some(format!("Unknown result flag")) }) }
        };
        push_results.push(
            PushResult{
                flag: result_flag,
                from: String::from_str(caps.at(2)),
                to: String::from_str(caps.at(3)),
                reason: String::from_str(caps.at(4))
            }
        )
    }
    Ok(push_results)
}

#[test]
fn test_parse_git_push_output_success() {
    let stdout = "To ssh://adam@127.0.0.1/Users/adam/src/opscode/delivery/opscode/delivery-cli2
=	refs/heads/foo:refs/heads/_for/master/foo	[up to date]
Done";
    let stderr = "Pushing to ssh://adam@Chef@172.31.6.130:8989/Chef/adam_universe/delivery-cli
Total 0 (delta 0), reused 0 (delta 0)
remote: Patchset already up to date, nothing to do
remote: https://172.31.6.130/e/Chef/#/organizations/adam_universe/projects/delivery-cli/changes/146a9573-1bd0-4a27-a106-528347761811
updating local tracking ref 'refs/remotes/origin/_for/master/adam/test6'";
    let result = parse_git_push_output(stdout, stderr);
    match result {
        Ok(pr_vec) => {
            // assert!(r_vec[0].flag, UpToDate);
            assert_eq!(pr_vec[0].from.as_slice(), "refs/heads/foo");
            assert_eq!(pr_vec[0].to.as_slice(), "refs/heads/_for/master/foo");
            assert_eq!(pr_vec[0].reason.as_slice(), "up to date");
        },
        Err(e) => panic!("No result")
    };
}

pub fn set_config(user: &str, server: &str, ent: &str, org: &str, proj: &str) -> Result<(), DeliveryError> {
    let result = git_command(&["remote", "add", "delivery", format!("ssh://{}@{}@{}:8989/{}/{}/{}", user, ent, server, ent, org, proj).as_slice()]);
    match result {
        Ok(_) => return Ok(()),
        Err(e) => {
            match e.detail {
                Some(msg) => {
                    if msg.contains("remote delivery already exists") {
                        return Err(DeliveryError{ kind: Kind::GitSetupFailed, detail: None });
                    }
                },
                None => {
                    return Err(e)
                }
            }
        },
    }
    Ok(())
}
<commit_msg>Ignoring e<commit_after>#[phase(plugin)]
extern crate regex_macros;
extern crate regex;
extern crate serialize;
#[phase(plugin, link)] extern crate log;

pub use errors;

use std::io::process::Command;
use utils::say::{sayln, spinner};
use errors::{DeliveryError, Kind};

pub fn get_head() -> Result<String, DeliveryError> {
    let gitr = try!(git_command(&["branch"]));
    let result = try!(parse_get_head(gitr.stdout.as_slice()));
    Ok(result)
}

fn parse_get_head(stdout: &str) -> Result<String, DeliveryError> {
    for line in stdout.lines_any() {
        let r = regex!(r"(.) (.+)");
        let caps_result = r.captures(line);
        let caps = match caps_result {
            Some(caps) => caps,
            None => { return Err(DeliveryError{ kind: Kind::BadGitOutputMatch, detail: Some(format!("Failed to match: {}", line)) }) }
        };
        if caps.at(1) == "*" {
            return Ok(String::from_str(caps.at(2)));
        }
    }
    return Err(DeliveryError{ kind: Kind::NotOnABranch, detail: None });
}

#[test]
fn test_parse_get_head() {
    let stdout = "  adam/review
  adam/test
  adam/test6
  builder
  first
  foo
  foo2
* master
  snazzy
  testerton";
    let result = parse_get_head(stdout);
    match result {
        Ok(branch) => {
            assert_eq!(branch.as_slice(), "master");
        },
        Err(e) => panic!("No result")
    };
}

pub struct GitResult {
    stdout: String,
    stderr: String
}

fn git_command(args: &[&str]) -> Result<GitResult, DeliveryError> {
    let (tx, rx) = channel::<int>();
    let (tx2, rx2) = channel::<int>();
    spawn(proc() spinner(rx, tx2));
    let mut command = Command::new("git");
    command.args(args);
    debug!("Git command: {}", command);
    let output = match command.output() {
        Ok(o) => o,
        Err(e) => { tx.send(1); return Err(DeliveryError{ kind: Kind::FailedToExecute, detail: Some(format!("failed to execute git: {}", e.desc))}) },
    };
    debug!("Git exited: {}", output.status);
    tx.send(1);
    rx2.recv();
    if !output.status.success() {
        return Err(DeliveryError{ kind: Kind::GitFailed, detail: Some(format!("STDOUT: {}\nSTDERR: {}\n", String::from_utf8_lossy(output.output.as_slice()), String::from_utf8_lossy(output.error.as_slice())))});
    }
    let stdout = String::from_utf8_lossy(output.output.as_slice()).into_string();
    debug!("Git stdout: {}", stdout);
    let stderr = String::from_utf8_lossy(output.error.as_slice()).into_string();
    debug!("Git stderr: {}", stderr);
    Ok(GitResult{ stdout: stdout, stderr: stderr })
}

pub fn git_push(branch: &str, target: &str) -> Result<String, DeliveryError> {
    let gitr = try!(git_command(&[
                     "push", "--porcelain", "--progress", "--verbose", "delivery", format!("{}:_for/{}/{}", branch, target, branch).as_slice()
                     ]));
    let output = try!(parse_git_push_output(gitr.stdout.as_slice(), gitr.stderr.as_slice()));
    for result in output.iter() {
        match result.flag {
            PushResultFlags::SuccessfulFastForward => sayln("green", format!("Updated change: {}", result.reason).as_slice()),
            PushResultFlags::SuccessfulForcedUpdate => sayln("green", format!("Force updated change: {}", result.reason).as_slice()),
            PushResultFlags::SuccessfulDeletedRef => sayln("red", format!("Deleted change: {}", result.reason).as_slice()),
            PushResultFlags::SuccessfulPushedNewRef => sayln("green", format!("Created change: {}", result.reason).as_slice()),
            PushResultFlags::Rejected => sayln("red", format!("Rejected change: {}", result.reason).as_slice()),
            PushResultFlags::UpToDate => sayln("yellow", format!("Nothing added to the existing change").as_slice()),
        }
    }
    Ok(gitr.stdout.into_string())
}

pub enum PushResultFlags {
    SuccessfulFastForward,
    SuccessfulForcedUpdate,
    SuccessfulDeletedRef,
    SuccessfulPushedNewRef,
    Rejected,
    UpToDate,
}

pub struct PushResult {
    flag: PushResultFlags,
    from: String,
    to: String,
    reason: String
}

pub fn parse_git_push_output(push_output: &str, push_error: &str) -> Result<Vec<PushResult>, DeliveryError> {
    let mut push_results: Vec<PushResult> = Vec::new();
    for line in push_error.lines_any() {
        debug!("error: {}", line);
        if line.starts_with("remote") {
            let r = regex!(r"remote: (.+)");
            let caps_result = r.captures(line);
            match caps_result {
                Some(caps) => sayln("white", format!("{}", caps.at(1)).as_slice()),
                None => {}
            }
        }
    }
    for line in push_output.lines_any() {
        debug!("output: {}", line);
        if line.starts_with("To") {
            continue;
        } else if line.starts_with("Done") {
            continue;
        }
        let r = regex!(r"(.)\t(.+):(.+)\t\[(.+)\]");
        let caps_result = r.captures(line);
        let caps = match caps_result {
            Some(caps) => caps,
            None => { return Err(DeliveryError{ kind: Kind::BadGitOutputMatch, detail: Some(format!("Failed to match: {}", line)) }) }
        };
        let result_flag = match caps.at(1) {
            " " => PushResultFlags::SuccessfulFastForward,
            "+" => PushResultFlags::SuccessfulForcedUpdate,
            "-" => PushResultFlags::SuccessfulDeletedRef,
            "*" => PushResultFlags::SuccessfulPushedNewRef,
            "!" => PushResultFlags::Rejected,
            "=" => PushResultFlags::UpToDate,
            _ => { return Err(DeliveryError{ kind: Kind::BadGitOutputMatch, detail: Some(format!("Unknown result flag")) }) }
        };
        push_results.push(
            PushResult{
                flag: result_flag,
                from: String::from_str(caps.at(2)),
                to: String::from_str(caps.at(3)),
                reason: String::from_str(caps.at(4))
            }
        )
    }
    Ok(push_results)
}

#[test]
fn test_parse_git_push_output_success() {
    let stdout = "To ssh://adam@127.0.0.1/Users/adam/src/opscode/delivery/opscode/delivery-cli2
=	refs/heads/foo:refs/heads/_for/master/foo	[up to date]
Done";
    let stderr = "Pushing to ssh://adam@Chef@172.31.6.130:8989/Chef/adam_universe/delivery-cli
Total 0 (delta 0), reused 0 (delta 0)
remote: Patchset already up to date, nothing to do
remote: https://172.31.6.130/e/Chef/#/organizations/adam_universe/projects/delivery-cli/changes/146a9573-1bd0-4a27-a106-528347761811
updating local tracking ref 'refs/remotes/origin/_for/master/adam/test6'";
    let result = parse_git_push_output(stdout, stderr);
    match result {
        Ok(pr_vec) => {
            assert_eq!(pr_vec[0].from.as_slice(), "refs/heads/foo");
            assert_eq!(pr_vec[0].to.as_slice(), "refs/heads/_for/master/foo");
            assert_eq!(pr_vec[0].reason.as_slice(), "up to date");
        },
        Err(_) => panic!("No result")
    };
}

pub fn set_config(user: &str, server: &str, ent: &str, org: &str, proj: &str) -> Result<(), DeliveryError> {
    let result = git_command(&["remote", "add", "delivery", format!("ssh://{}@{}@{}:8989/{}/{}/{}", user, ent, server, ent, org, proj).as_slice()]);
    match result {
        Ok(_) => return Ok(()),
        Err(e) => {
            match e.detail {
                Some(msg) => {
                    if msg.contains("remote delivery already exists") {
                        return Err(DeliveryError{ kind: Kind::GitSetupFailed, detail: None });
                    }
                },
                None => {
                    return Err(e)
                }
            }
        },
    }
    Ok(())
}
<|endoftext|>


<commit_before>extern crate regex;

#[derive(Debug)]
pub enum CommandOperator {
    AND,
    OR,
    ALWAYS,
    PIPE,
    RIGHT,
    LEFT
}

#[derive(Debug)]
pub struct CommandResult {
    pub status: i32,
    pub output: Option<String>
}

#[derive(Debug)]
struct Command {
    command: Vec<String>,
    command_operator: CommandOperator
}

fn clean_cmd(mut cmd: String) -> String {
    match cmd.chars().last() {
        Some(c) => {if c == '\n' { cmd.pop(); }},
        _ => {}
    }
    return cmd.trim().to_string();
}

fn get_all_commands(command: &String) -> Vec<Command>{
    let mut commands: Vec<Command> = vec![];
    let re= regex::Regex::new(r";|&&|\|\||\||>|<").unwrap();
    let mut caps = re.captures_iter(command);

    for mut part in re.split(command) {
        let cmd: String = clean_cmd(part.to_string());
        let cmd_vector = cmd.split(" ").map(|x: &str| x.to_string()).collect::<Vec<String>>();
        commands.push(Command {
            command: cmd_vector,
            command_operator:
            match caps.next() {
                Some(t) => {
                    let m = t.get(0).unwrap().as_str();
                    if m == ";" {
                        CommandOperator::ALWAYS
                    } else if m == "&&" {
                        CommandOperator::AND
                    } else if m == "||" {
                        CommandOperator::OR
                    } else if m == "|" {
                        CommandOperator::PIPE
                    } else if m == ">" {
                        CommandOperator::RIGHT
                    } else if m == "<" {
                        CommandOperator::LEFT
                    } else {
                        CommandOperator::ALWAYS
                    }
                },
                _ => {
                    CommandOperator::ALWAYS
                }
            }
        });
    }
    return commands;
}

fn command_parser(mut command_vector : &mut Vec<String>, operator: &CommandOperator, input: &Option<String>) -> CommandResult {
    use cd::cd;
    use launch_bin::launch_bin;
    use env::{env, setenv, unsetenv};

    match command_vector[0].trim() {
        "cd"        => return cd(&command_vector),
        "env"       => return env(),
        "setenv"    => return setenv(&mut command_vector),
        "unsetenv"  => return unsetenv(&mut command_vector),
        ""          => {return CommandResult {status: 0, output: None}},
        _           => return launch_bin(&mut command_vector, operator, input)
    }
}

fn handle_and(cmd: &mut Command, cmd_result: &mut CommandResult) {
    if cmd_result.status != 0 {
        return ;
    } else {
        *cmd_result = command_parser(&mut cmd.command, &cmd.command_operator,&cmd_result.output);
    }
}

fn handle_or(cmd: &mut Command, cmd_result: &mut CommandResult) {
    if cmd_result.status == 0 {
        return;
    } else {
        *cmd_result = command_parser(&mut cmd.command, &cmd.command_operator,&cmd_result.output);
    }
}

fn handle_right(cmd: &mut Command, cmd_result: &mut CommandResult) {
    use std::fs::File;
    use std::io::prelude::*;

    let mut f = match File::create(&cmd.command[0]) {
        Ok(file) => {file},
        Err(e) => {println!("Could not open file : {}", e); return ;}
    };
    match &cmd_result.output {
        &Some(ref output) => {
            match f.write_all(output.as_bytes()) {
                Ok(_) => {},
                Err(e) => {println!("Error writing to file : {}", e);}
            };
        },
        &None => {}
    }
}

pub fn handle_command(command: &mut String) {

    let mut cmd_result: CommandResult = CommandResult {status: 0, output: None};
    let all_commands: Vec<Command> = get_all_commands(command);
    let mut operator = CommandOperator::ALWAYS;

    for mut cmd in all_commands {
        match operator {
            CommandOperator::AND => { handle_and(&mut cmd, &mut cmd_result); },
            CommandOperator::OR  => { handle_or(&mut cmd, &mut cmd_result); },
            CommandOperator::RIGHT => { handle_right(&mut cmd, &mut cmd_result); }
            _ => { cmd_result = command_parser(&mut cmd.command, &cmd.command_operator,&cmd_result.output); }
        }
        operator = cmd.command_operator;
    }
}
<commit_msg>handle right redirection<commit_after>extern crate regex;

#[derive(Debug)]
pub enum CommandOperator {
    AND,
    OR,
    NONE,
    PIPE,
    RIGHT,
    LEFT
}

#[derive(Debug)]
pub struct CommandResult {
    pub status: i32,
    pub output: Option<String>
}

#[derive(Debug)]
struct Command {
    command: Vec<String>,
    command_operator: CommandOperator
}

fn clean_cmd(mut cmd: String) -> String {
    match cmd.chars().last() {
        Some(c) => {if c == '\n' { cmd.pop(); }},
        _ => {}
    }
    return cmd.trim().to_string();
}

fn get_all_commands(command: &String) -> Vec<Command>{
    let mut commands: Vec<Command> = vec![];
    let re= regex::Regex::new(r";|&&|\|\||\||>|<").unwrap();
    let mut caps = re.captures_iter(command);

    for mut part in re.split(command) {
        let cmd: String = clean_cmd(part.to_string());
        let cmd_vector = cmd.split(" ").map(|x: &str| x.to_string()).collect::<Vec<String>>();
        let op = match caps.next() {
            Some(t) => {t.get(0).unwrap().as_str()},
            None => ""
            };

        commands.push(Command {
            command: cmd_vector,
            command_operator:
            match op {
                ";" => CommandOperator::NONE,
                "&&" => CommandOperator::AND,
                "||" => CommandOperator::OR,
                "|" => CommandOperator::PIPE,
                ">" => CommandOperator::RIGHT,
                "<" => CommandOperator::LEFT,
                _ => CommandOperator::NONE
            }
        });
    }
    return commands;
}

fn command_parser(mut command_vector : &mut Vec<String>, operator: &CommandOperator, input: &Option<String>) -> CommandResult {
    use cd::cd;
    use launch_bin::launch_bin;
    use env::{env, setenv, unsetenv};

    match command_vector[0].trim() {
        "cd"        => return cd(&command_vector),
        "env"       => return env(),
        "setenv"    => return setenv(&mut command_vector),
        "unsetenv"  => return unsetenv(&mut command_vector),
        ""          => {return CommandResult {status: 0, output: None}},
        _           => return launch_bin(&mut command_vector, operator, input)
    }
}

fn handle_and(cmd: &mut Command, cmd_result: &mut CommandResult) {
    if cmd_result.status != 0 {
        return ;
    } else {
        *cmd_result = command_parser(&mut cmd.command, &cmd.command_operator,&cmd_result.output);
    }
}

fn handle_or(cmd: &mut Command, cmd_result: &mut CommandResult) {
    if cmd_result.status == 0 {
        return;
    } else {
        *cmd_result = command_parser(&mut cmd.command, &cmd.command_operator,&cmd_result.output);
    }
}

fn handle_right(cmd: &mut Command, cmd_result: &mut CommandResult) {
    use std::fs::File;
    use std::io::prelude::*;

    let mut f = match File::create(&cmd.command[0]) {
        Ok(file) => {file},
        Err(e) => {println!("Could not open file : {}", e); return ;}
    };
    match &cmd_result.output {
        &Some(ref output) => {
            match f.write_all(output.as_bytes()) {
                Ok(_) => {},
                Err(e) => {println!("Error writing to file : {}", e);}
            };
        },
        &None => {}
    }
}

pub fn handle_command(command: &mut String) {

    let mut cmd_result: CommandResult = CommandResult {status: 0, output: None};
    let all_commands: Vec<Command> = get_all_commands(command);
    let mut operator = CommandOperator::NONE;
    let mut idx = 0;

    for mut cmd in all_commands {
        match operator {
            CommandOperator::AND => { handle_and(&mut cmd, &mut cmd_result); },
            CommandOperator::OR  => { handle_or(&mut cmd, &mut cmd_result); },
            CommandOperator::RIGHT => { handle_right(&mut cmd, &mut cmd_result); }
            _ => { cmd_result = command_parser(&mut cmd.command, &cmd.command_operator,&cmd_result.output); }
        }
        idx += 1;
        operator = cmd.command_operator;
    }
}
<|endoftext|>


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

//! Implementation of ElasticSearch Analyze operation

use std::collections::BTreeMap;
use rustc_serialize::json::{Json, ToJson};

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

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

    index:    Option<&'b str>,
    req:      AnalyzeRequest<'b>
}

struct AnalyzeRequest<'b> {
    body:     &'b str,
    analyzer: Option<&'b str>
}

impl<'b> ToJson for AnalyzeRequest<'b> {
    fn to_json(&self) -> Json {
        let mut d = BTreeMap::new();

        d.insert("body".to_owned(), self.body.to_json());
        optional_add!(d, self.analyzer, "analyzer");

        Json::Object(d)
    }
}

impl<'a, 'b> AnalyzeOperation<'a, 'b> {
    pub fn new(client: &'a mut Client, body: &'b str) -> AnalyzeOperation<'a, 'b> {
        AnalyzeOperation {
            client:   client,
            index:    None,
            req: AnalyzeRequest {
                body:     body,
                analyzer: None
            }
        }
    }

    pub fn with_index(&'b mut self, index: &'b str) -> &'b mut Self {
        self.index = Some(index);
        self
    }

    pub fn with_analyzer(&'b mut self, analyzer: &'b str) -> &'b mut Self {
        self.req.analyzer = Some(analyzer);
        self
    }

    pub fn send(&'b mut self) -> Result<AnalyzeResult, EsError> {
        let url = match self.index {
            None => "/_analyze".to_owned(),
            Some(index) => format!("/{}/_analyze", index)
        };
        let (_, result) = try!(self.client.post_body_op(&url, &self.req.to_json()));
        Ok(AnalyzeResult::from(&result.expect("No Json payload")))
    }
}

/// The result of an analyze operation
#[derive(Debug)]
pub struct AnalyzeResult {
    pub tokens: Vec<Token>
}

#[derive(Debug)]
pub struct Token {
    pub token: String,
    pub token_type: String,
    pub position: u64,
    pub start_offset: u64,
    pub end_offset: u64
}

impl<'a> From<&'a Json> for AnalyzeResult {
    fn from(r: &'a Json) -> AnalyzeResult {
        let mut tokens = Vec::new();
        for t in get_json_array!(r, "tokens") {
            tokens.push(Token {
                token: get_json_string!(t, "token"),
                token_type: get_json_string!(t, "type"),
                position: get_json_u64!(t, "position"),
                start_offset: get_json_u64!(t, "start_offset"),
                end_offset: get_json_u64!(t, "end_offset")
            })
        }
        AnalyzeResult {
            tokens: tokens
        }
    }
}
<commit_msg>analyze: fixes, drop AnalyzeRequest<commit_after>/*
 * Copyright 2015 Ben Ashford
 * Copyright 2015 Astro
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

//! Implementation of ElasticSearch Analyze operation

use std::collections::BTreeMap;
use rustc_serialize::json::{Json, ToJson};

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

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

    body:     &'b str,
    index:    Option<&'b str>,
    analyzer: Option<&'b str>
}

impl<'a, 'b> AnalyzeOperation<'a, 'b> {
    pub fn new(client: &'a mut Client, body: &'b str) -> AnalyzeOperation<'a, 'b> {
        AnalyzeOperation {
            client:   client,
            body:     body,
            index:    None,
            analyzer: None
        }
    }

    pub fn with_index(&'b mut self, index: &'b str) -> &'b mut Self {
        self.index = Some(index);
        self
    }

    pub fn with_analyzer(&'b mut self, analyzer: &'b str) -> &'b mut Self {
        self.analyzer = Some(analyzer);
        self
    }

    pub fn send(&'b mut self) -> Result<AnalyzeResult, EsError> {
        let mut url = match self.index {
            None => "/_analyze".to_owned(),
            Some(index) => format!("{}/_analyze", index)
        };
        match self.analyzer {
            None => (),
            Some(analyzer) => {
                url.push_str(&format!("?analyzer={}", analyzer))
            }
        }
        let (_, result) = try!(self.client.post_body_op(&url, &self.body));
        Ok(AnalyzeResult::from(&result.expect("No Json payload")))
    }
}

/// The result of an analyze operation
#[derive(Debug)]
pub struct AnalyzeResult {
    pub tokens: Vec<Token>
}

#[derive(Debug)]
pub struct Token {
    pub token: String,
    pub token_type: String,
    pub position: u64,
    pub start_offset: u64,
    pub end_offset: u64
}

impl<'a> From<&'a Json> for AnalyzeResult {
    fn from(r: &'a Json) -> AnalyzeResult {
        let mut tokens = Vec::new();
        for t in get_json_array!(r, "tokens") {
            tokens.push(Token {
                token: get_json_string!(t, "token"),
                token_type: get_json_string!(t, "type"),
                position: get_json_u64!(t, "position"),
                start_offset: get_json_u64!(t, "start_offset"),
                end_offset: get_json_u64!(t, "end_offset")
            })
        }
        AnalyzeResult {
            tokens: tokens
        }
    }
}
<|endoftext|>


<commit_before>use std::{
    fmt,
    hash::{Hash, Hasher},
};

use clippy_utils::{diagnostics::span_lint_and_help, in_macro, is_direct_expn_of, source::snippet_opt};
use if_chain::if_chain;
use rustc_ast::ast;
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_lint::{EarlyContext, EarlyLintPass};
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::Span;
use serde::{de, Deserialize};

declare_clippy_lint! {
    /// **What it does:** Checks that common macros are used with consistent bracing.
    ///
    /// **Why is this bad?** This is mostly a consistency lint although using () or []
    /// doesn't give you a semicolon in item position, which can be unexpected.
    ///
    /// **Known problems:**
    /// None
    ///
    /// **Example:**
    ///
    /// ```rust
    /// vec!{1, 2, 3};
    /// ```
    /// Use instead:
    /// ```rust
    /// vec![1, 2, 3];
    /// ```
    pub NONSTANDARD_MACRO_BRACES,
    nursery,
    "check consistent use of braces in macro"
}

const BRACES: &[(&str, &str)] = &[("(", ")"), ("{", "}"), ("[", "]")];

/// The (name, (open brace, close brace), source snippet)
type MacroInfo<'a> = (&'a str, &'a (String, String), String);

#[derive(Clone, Debug, Default)]
pub struct MacroBraces {
    macro_braces: FxHashMap<String, (String, String)>,
    done: FxHashSet<Span>,
}

impl MacroBraces {
    pub fn new(conf: &FxHashSet<MacroMatcher>) -> Self {
        let macro_braces = macro_braces(conf.clone());
        Self {
            macro_braces,
            done: FxHashSet::default(),
        }
    }
}

impl_lint_pass!(MacroBraces => [NONSTANDARD_MACRO_BRACES]);

impl EarlyLintPass for MacroBraces {
    fn check_item(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) {
        if let Some((name, braces, snip)) = is_offending_macro(cx, item.span, self) {
            let span = item.span.ctxt().outer_expn_data().call_site;
            emit_help(cx, snip, braces, name, span);
            self.done.insert(span);
        }
    }

    fn check_stmt(&mut self, cx: &EarlyContext<'_>, stmt: &ast::Stmt) {
        if let Some((name, braces, snip)) = is_offending_macro(cx, stmt.span, self) {
            let span = stmt.span.ctxt().outer_expn_data().call_site;
            emit_help(cx, snip, braces, name, span);
            self.done.insert(span);
        }
    }

    fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &ast::Expr) {
        if let Some((name, braces, snip)) = is_offending_macro(cx, expr.span, self) {
            let span = expr.span.ctxt().outer_expn_data().call_site;
            emit_help(cx, snip, braces, name, span);
            self.done.insert(span);
        }
    }

    fn check_ty(&mut self, cx: &EarlyContext<'_>, ty: &ast::Ty) {
        if let Some((name, braces, snip)) = is_offending_macro(cx, ty.span, self) {
            let span = ty.span.ctxt().outer_expn_data().call_site;
            emit_help(cx, snip, braces, name, span);
            self.done.insert(span);
        }
    }
}

fn is_offending_macro<'a>(cx: &EarlyContext<'_>, span: Span, mac_braces: &'a MacroBraces) -> Option<MacroInfo<'a>> {
    if_chain! {
        if in_macro(span);
        if let Some((name, braces)) = find_matching_macro(span, &mac_braces.macro_braces);
        if let Some(snip) = snippet_opt(cx, span.ctxt().outer_expn_data().call_site);
        // we must check only invocation sites
        // https://github.com/rust-lang/rust-clippy/issues/7422
        if snip.starts_with(name);
        // make formatting consistent
        let c = snip.replace(" ", "");
        if !c.starts_with(&format!("{}!{}", name, braces.0));
        if !mac_braces.done.contains(&span.ctxt().outer_expn_data().call_site);
        then {
            Some((name, braces, snip))
        } else {
            None
        }
    }
}

fn emit_help(cx: &EarlyContext<'_>, snip: String, braces: &(String, String), name: &str, span: Span) {
    let with_space = &format!("! {}", braces.0);
    let without_space = &format!("!{}", braces.0);
    let mut help = snip;
    for b in BRACES.iter().filter(|b| b.0 != braces.0) {
        help = help.replace(b.0, &braces.0).replace(b.1, &braces.1);
        // Only `{` traditionally has space before the brace
        if braces.0 != "{" && help.contains(with_space) {
            help = help.replace(with_space, without_space);
        } else if braces.0 == "{" && help.contains(without_space) {
            help = help.replace(without_space, with_space);
        }
    }
    span_lint_and_help(
        cx,
        NONSTANDARD_MACRO_BRACES,
        span,
        &format!("use of irregular braces for `{}!` macro", name),
        Some(span),
        &format!("consider writing `{}`", help),
    );
}

fn find_matching_macro(
    span: Span,
    braces: &FxHashMap<String, (String, String)>,
) -> Option<(&String, &(String, String))> {
    braces
        .iter()
        .find(|(macro_name, _)| is_direct_expn_of(span, macro_name).is_some())
}

fn macro_braces(conf: FxHashSet<MacroMatcher>) -> FxHashMap<String, (String, String)> {
    let mut braces = vec![
        macro_matcher!(
            name: "print",
            braces: ("(", ")"),
        ),
        macro_matcher!(
            name: "println",
            braces: ("(", ")"),
        ),
        macro_matcher!(
            name: "eprint",
            braces: ("(", ")"),
        ),
        macro_matcher!(
            name: "eprintln",
            braces: ("(", ")"),
        ),
        macro_matcher!(
            name: "write",
            braces: ("(", ")"),
        ),
        macro_matcher!(
            name: "writeln",
            braces: ("(", ")"),
        ),
        macro_matcher!(
            name: "format",
            braces: ("(", ")"),
        ),
        macro_matcher!(
            name: "format_args",
            braces: ("(", ")"),
        ),
        macro_matcher!(
            name: "vec",
            braces: ("[", "]"),
        ),
    ]
    .into_iter()
    .collect::<FxHashMap<_, _>>();
    // We want users items to override any existing items
    for it in conf {
        braces.insert(it.name, it.braces);
    }
    braces
}

macro_rules! macro_matcher {
    (name: $name:expr, braces: ($open:expr, $close:expr) $(,)?) => {
        ($name.to_owned(), ($open.to_owned(), $close.to_owned()))
    };
}
pub(crate) use macro_matcher;

#[derive(Clone, Debug)]
pub struct MacroMatcher {
    name: String,
    braces: (String, String),
}

impl Hash for MacroMatcher {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.name.hash(state);
    }
}

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

impl<'de> Deserialize<'de> for MacroMatcher {
    fn deserialize<D>(deser: D) -> Result<Self, D::Error>
    where
        D: de::Deserializer<'de>,
    {
        #[derive(Deserialize)]
        #[serde(field_identifier, rename_all = "lowercase")]
        enum Field {
            Name,
            Brace,
        }
        struct MacVisitor;
        impl<'de> de::Visitor<'de> for MacVisitor {
            type Value = MacroMatcher;

            fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
                formatter.write_str("struct MacroMatcher")
            }

            fn visit_map<V>(self, mut map: V) -> Result<Self::Value, V::Error>
            where
                V: de::MapAccess<'de>,
            {
                let mut name = None;
                let mut brace: Option<&str> = None;
                while let Some(key) = map.next_key()? {
                    match key {
                        Field::Name => {
                            if name.is_some() {
                                return Err(de::Error::duplicate_field("name"));
                            }
                            name = Some(map.next_value()?);
                        },
                        Field::Brace => {
                            if brace.is_some() {
                                return Err(de::Error::duplicate_field("brace"));
                            }
                            brace = Some(map.next_value()?);
                        },
                    }
                }
                let name = name.ok_or_else(|| de::Error::missing_field("name"))?;
                let brace = brace.ok_or_else(|| de::Error::missing_field("brace"))?;
                Ok(MacroMatcher {
                    name,
                    braces: BRACES
                        .iter()
                        .find(|b| b.0 == brace)
                        .map(|(o, c)| ((*o).to_owned(), (*c).to_owned()))
                        .ok_or_else(|| {
                            de::Error::custom(&format!("expected one of `(`, `{{`, `[` found `{}`", brace))
                        })?,
                })
            }
        }

        const FIELDS: &[&str] = &["name", "brace"];
        deser.deserialize_struct("MacroMatcher", FIELDS, MacVisitor)
    }
}
<commit_msg>Fix FP for nonstandard_macro_braces lint<commit_after>use std::{
    fmt,
    hash::{Hash, Hasher},
};

use clippy_utils::{diagnostics::span_lint_and_help, in_macro, is_direct_expn_of, source::snippet_opt};
use if_chain::if_chain;
use rustc_ast::ast;
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_lint::{EarlyContext, EarlyLintPass};
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::Span;
use serde::{de, Deserialize};

declare_clippy_lint! {
    /// **What it does:** Checks that common macros are used with consistent bracing.
    ///
    /// **Why is this bad?** This is mostly a consistency lint although using () or []
    /// doesn't give you a semicolon in item position, which can be unexpected.
    ///
    /// **Known problems:**
    /// None
    ///
    /// **Example:**
    ///
    /// ```rust
    /// vec!{1, 2, 3};
    /// ```
    /// Use instead:
    /// ```rust
    /// vec![1, 2, 3];
    /// ```
    pub NONSTANDARD_MACRO_BRACES,
    nursery,
    "check consistent use of braces in macro"
}

const BRACES: &[(&str, &str)] = &[("(", ")"), ("{", "}"), ("[", "]")];

/// The (name, (open brace, close brace), source snippet)
type MacroInfo<'a> = (&'a str, &'a (String, String), String);

#[derive(Clone, Debug, Default)]
pub struct MacroBraces {
    macro_braces: FxHashMap<String, (String, String)>,
    done: FxHashSet<Span>,
}

impl MacroBraces {
    pub fn new(conf: &FxHashSet<MacroMatcher>) -> Self {
        let macro_braces = macro_braces(conf.clone());
        Self {
            macro_braces,
            done: FxHashSet::default(),
        }
    }
}

impl_lint_pass!(MacroBraces => [NONSTANDARD_MACRO_BRACES]);

impl EarlyLintPass for MacroBraces {
    fn check_item(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) {
        if let Some((name, braces, snip)) = is_offending_macro(cx, item.span, self) {
            let span = item.span.ctxt().outer_expn_data().call_site;
            emit_help(cx, snip, braces, name, span);
            self.done.insert(span);
        }
    }

    fn check_stmt(&mut self, cx: &EarlyContext<'_>, stmt: &ast::Stmt) {
        if let Some((name, braces, snip)) = is_offending_macro(cx, stmt.span, self) {
            let span = stmt.span.ctxt().outer_expn_data().call_site;
            emit_help(cx, snip, braces, name, span);
            self.done.insert(span);
        }
    }

    fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &ast::Expr) {
        if let Some((name, braces, snip)) = is_offending_macro(cx, expr.span, self) {
            let span = expr.span.ctxt().outer_expn_data().call_site;
            emit_help(cx, snip, braces, name, span);
            self.done.insert(span);
        }
    }

    fn check_ty(&mut self, cx: &EarlyContext<'_>, ty: &ast::Ty) {
        if let Some((name, braces, snip)) = is_offending_macro(cx, ty.span, self) {
            let span = ty.span.ctxt().outer_expn_data().call_site;
            emit_help(cx, snip, braces, name, span);
            self.done.insert(span);
        }
    }
}

fn is_offending_macro<'a>(cx: &EarlyContext<'_>, span: Span, mac_braces: &'a MacroBraces) -> Option<MacroInfo<'a>> {
    if_chain! {
        if in_macro(span);
        if let Some((name, braces)) = find_matching_macro(span, &mac_braces.macro_braces);
        if let Some(snip) = snippet_opt(cx, span.ctxt().outer_expn_data().call_site);
        // we must check only invocation sites
        // https://github.com/rust-lang/rust-clippy/issues/7422
        if snip.starts_with(&format!("{}!", name));
        // make formatting consistent
        let c = snip.replace(" ", "");
        if !c.starts_with(&format!("{}!{}", name, braces.0));
        if !mac_braces.done.contains(&span.ctxt().outer_expn_data().call_site);
        then {
            Some((name, braces, snip))
        } else {
            None
        }
    }
}

fn emit_help(cx: &EarlyContext<'_>, snip: String, braces: &(String, String), name: &str, span: Span) {
    let with_space = &format!("! {}", braces.0);
    let without_space = &format!("!{}", braces.0);
    let mut help = snip;
    for b in BRACES.iter().filter(|b| b.0 != braces.0) {
        help = help.replace(b.0, &braces.0).replace(b.1, &braces.1);
        // Only `{` traditionally has space before the brace
        if braces.0 != "{" && help.contains(with_space) {
            help = help.replace(with_space, without_space);
        } else if braces.0 == "{" && help.contains(without_space) {
            help = help.replace(without_space, with_space);
        }
    }
    span_lint_and_help(
        cx,
        NONSTANDARD_MACRO_BRACES,
        span,
        &format!("use of irregular braces for `{}!` macro", name),
        Some(span),
        &format!("consider writing `{}`", help),
    );
}

fn find_matching_macro(
    span: Span,
    braces: &FxHashMap<String, (String, String)>,
) -> Option<(&String, &(String, String))> {
    braces
        .iter()
        .find(|(macro_name, _)| is_direct_expn_of(span, macro_name).is_some())
}

fn macro_braces(conf: FxHashSet<MacroMatcher>) -> FxHashMap<String, (String, String)> {
    let mut braces = vec![
        macro_matcher!(
            name: "print",
            braces: ("(", ")"),
        ),
        macro_matcher!(
            name: "println",
            braces: ("(", ")"),
        ),
        macro_matcher!(
            name: "eprint",
            braces: ("(", ")"),
        ),
        macro_matcher!(
            name: "eprintln",
            braces: ("(", ")"),
        ),
        macro_matcher!(
            name: "write",
            braces: ("(", ")"),
        ),
        macro_matcher!(
            name: "writeln",
            braces: ("(", ")"),
        ),
        macro_matcher!(
            name: "format",
            braces: ("(", ")"),
        ),
        macro_matcher!(
            name: "format_args",
            braces: ("(", ")"),
        ),
        macro_matcher!(
            name: "vec",
            braces: ("[", "]"),
        ),
    ]
    .into_iter()
    .collect::<FxHashMap<_, _>>();
    // We want users items to override any existing items
    for it in conf {
        braces.insert(it.name, it.braces);
    }
    braces
}

macro_rules! macro_matcher {
    (name: $name:expr, braces: ($open:expr, $close:expr) $(,)?) => {
        ($name.to_owned(), ($open.to_owned(), $close.to_owned()))
    };
}
pub(crate) use macro_matcher;

#[derive(Clone, Debug)]
pub struct MacroMatcher {
    name: String,
    braces: (String, String),
}

impl Hash for MacroMatcher {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.name.hash(state);
    }
}

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

impl<'de> Deserialize<'de> for MacroMatcher {
    fn deserialize<D>(deser: D) -> Result<Self, D::Error>
    where
        D: de::Deserializer<'de>,
    {
        #[derive(Deserialize)]
        #[serde(field_identifier, rename_all = "lowercase")]
        enum Field {
            Name,
            Brace,
        }
        struct MacVisitor;
        impl<'de> de::Visitor<'de> for MacVisitor {
            type Value = MacroMatcher;

            fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
                formatter.write_str("struct MacroMatcher")
            }

            fn visit_map<V>(self, mut map: V) -> Result<Self::Value, V::Error>
            where
                V: de::MapAccess<'de>,
            {
                let mut name = None;
                let mut brace: Option<&str> = None;
                while let Some(key) = map.next_key()? {
                    match key {
                        Field::Name => {
                            if name.is_some() {
                                return Err(de::Error::duplicate_field("name"));
                            }
                            name = Some(map.next_value()?);
                        },
                        Field::Brace => {
                            if brace.is_some() {
                                return Err(de::Error::duplicate_field("brace"));
                            }
                            brace = Some(map.next_value()?);
                        },
                    }
                }
                let name = name.ok_or_else(|| de::Error::missing_field("name"))?;
                let brace = brace.ok_or_else(|| de::Error::missing_field("brace"))?;
                Ok(MacroMatcher {
                    name,
                    braces: BRACES
                        .iter()
                        .find(|b| b.0 == brace)
                        .map(|(o, c)| ((*o).to_owned(), (*c).to_owned()))
                        .ok_or_else(|| {
                            de::Error::custom(&format!("expected one of `(`, `{{`, `[` found `{}`", brace))
                        })?,
                })
            }
        }

        const FIELDS: &[&str] = &["name", "brace"];
        deser.deserialize_struct("MacroMatcher", FIELDS, MacVisitor)
    }
}
<|endoftext|>