use vec1::{Vec1, Size0Error}; use internals::grammar::encoded_word::EncodedWordContext; use internals::error::EncodingError; use internals::encoder::{EncodingWriter, EncodableInHeader}; use ::{HeaderTryFrom, HeaderTryInto}; use ::error::ComponentCreationError; use ::data::Input; use super::utils::text_partition::{ Partition, partition }; use super::word::{ Word, do_encode_word }; use super::{ CFWS, FWS }; /// Represent a "phrase" as it for example is used in the `Mailbox` type for the display name. /// /// It is recommended to use the [`Phrase.new()`] constructor, which creates the right phrase /// for your input. /// /// **Warning: Details of this type, expect `Phrase::new` and `Phrase::try_from`, are likely to /// change with some of the coming braking changes.** If you just create it using `try_from` /// or `new` changes should not affect you, but if you create it from a vec of `Word`'s things /// might be different. #[derive( Debug, Clone, Eq, PartialEq, Hash )] pub struct Phrase( //FIXME hide this away or at last turn it into a struct field, with next braking change. /// The "words" the phrase consist of. Be aware that this are words in the sense of the /// mail grammar so it can be a complete quoted string. Also be aware that in the mail /// grammar "words" _contain the whitespace around them_ (to some degree). So if you /// just have a sequence of "human words" turned into word instances there will be /// no whitespace between the words. (From the point of the mail grammar a words do not /// have to have any boundaries between each other even if this leads to ambiguity) pub Vec1 ); impl Phrase { /// Creates a `Phrase` instance from some arbitrary input. /// /// This method can be used with both `&str` and `String`. /// /// # Error /// /// There are only two cases in which this can fail: /// /// 1. If the input is empty (a phrase can not be empty). /// 2. If the input contained a illegal us-ascii character (any char which is /// not "visible" and not `' '` or `\t` like e.g. CTRL chars `'\0'` but also /// `'\r'` and `'\n'`). While we could encode them with encoded words, it's /// not really meant to be used this way and this chars will likely either be /// stripped out by a mail client or might cause display bugs. pub fn new>(input: T) -> Result { //TODO it would make much more sense if Input::shared could be taken advantage of let input = input.try_into()?; //OPTIMIZE: words => shared, then turn partition into shares, too let mut last_gap = None; let mut words = Vec::new(); let partitions = partition( input.as_str() ) .map_err(|err| ComponentCreationError ::from_parent(err, "Phrase") .with_str_context(input.as_str()) )?; for partition in partitions.into_iter() { match partition { Partition::VCHAR( word ) => { let mut word = Word::try_from( word )?; if let Some( fws ) = last_gap.take() { word.pad_left( fws ); } words.push( word ); }, Partition::SPACE( _gap ) => { //FIMXE currently collapses WS (This will leave at last one WS!) last_gap = Some( CFWS::SingleFws( FWS ) ) } } } let mut words = Vec1::from_vec(words) .map_err( |_| ComponentCreationError ::from_parent(Size0Error, "Phrase") .with_str_context(input.as_str()) )?; if let Some( right_padding ) = last_gap { words.last_mut().pad_right( right_padding ); } Ok( Phrase( words ) ) } } impl<'a> HeaderTryFrom<&'a str> for Phrase { fn try_from(input: &'a str) -> Result { Phrase::new(input) } } impl HeaderTryFrom for Phrase { fn try_from(input: String) -> Result { Phrase::new(input) } } impl HeaderTryFrom for Phrase { fn try_from(input: Input) -> Result { Phrase::new(input) } } impl EncodableInHeader for Phrase { //FEATURE_TODO(warn_on_bad_phrase): warn if the phrase contains chars it should not // but can contain due to encoding, e.g. ascii CTL's fn encode(&self, heandle: &mut EncodingWriter) -> Result<(), EncodingError> { for word in self.0.iter() { do_encode_word( &*word, heandle, Some( EncodedWordContext::Phrase ) )?; } Ok( () ) } fn boxed_clone(&self) -> Box { Box::new(self.clone()) } } #[cfg(test)] mod test { use ::HeaderTryFrom; use super::Phrase; ec_test!{ simple, { Phrase::try_from("simple think")? } => ascii => [ Text "simple", MarkFWS, Text " think" ]} ec_test!{ with_encoding, { Phrase::try_from(" hm nääds encoding")? } => ascii => [ MarkFWS, Text " hm", MarkFWS, Text " =?utf8?Q?n=C3=A4=C3=A4ds?=", MarkFWS, Text " encoding" ]} }