# 正则表达式

### 验证邮件格式并取出 @ 前的信息

下面代码使用 regex 包来验证邮件格式的正确性，然后提取出 `@` 符号前的所有内容。

    use lazy_static::lazy_static;

    use regex::Regex;

    fn extract_login(input: &str) -> Option<&str> {
        lazy_static! {
            static ref RE: Regex = Regex::new(r"(?x)
                ^(?P<login>[^@\s]+)@
                ([[:word:]]+\.)*
                [[:word:]]+$
                ").unwrap();
        }
        RE.captures(input).and_then(|cap| {
            cap.name("login").map(|login| login.as_str())
        })
    }

    fn main() {
        assert_eq!(extract_login(r"I❤email@example.com"), Some(r"I❤email"));
        assert_eq!(
            extract_login(r"sdf+sdsfsd.as.sdsd@jhkk.d.rl"),
            Some(r"sdf+sdsfsd.as.sdsd")
        );
        assert_eq!(extract_login(r"More@Than@One@at.com"), None);
        assert_eq!(extract_login(r"Not an email@email"), None);
    }

### 从文本中提出 # 开头的标签

例子对标签进行提取、排序和去重。需要注意，下面的标签仅仅是拉丁字母的，如果你要支持更多的字母，可以参考下 Twitter 的正则语法，友情提示，复杂的多！

    use lazy_static::lazy_static;

    use regex::Regex;
    use std::collections::HashSet;

    fn extract_hashtags(text: &str) -> HashSet<&str> {
        lazy_static! {
            static ref HASHTAG_REGEX : Regex = Regex::new(
                    r"\#[a-zA-Z][0-9a-zA-Z_]*"
                ).unwrap();
        }
        HASHTAG_REGEX.find_iter(text).map(|mat| mat.as_str()).collect()
    }

    fn main() {
        let tweet = "Hey #world, I just got my new #dog, say hello to Till. #dog #forever #2 #_ ";
        let tags = extract_hashtags(tweet);
        assert!(tags.contains("#dog") && tags.contains("#forever") && tags.contains("#world"));
        assert_eq!(tags.len(), 3);
    }

### 从文本中提取出所有手机号

[Regex::captures_iter] 可以对字符串型文本进行处理，以获取文本中的多个手机号。下面的例子适用于美国的号码。

    use error_chain::error_chain;

    use regex::Regex;
    use std::fmt;

    error_chain!{
       foreign_links {
           Regex(regex::Error);
           Io(std::io::Error);
       }
    }

    struct PhoneNumber<'a> {
        area: &'a str,
        exchange: &'a str,
        subscriber: &'a str,
    }

    impl<'a> fmt::Display for PhoneNumber<'a> {
        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
            write!(f, "1 ({}) {}-{}", self.area, self.exchange, self.subscriber)
        }
    }

    fn main() -> Result<()> {
        let phone_text = "
        +1 505 881 9292 (v) +1 505 778 2212 (c) +1 505 881 9297 (f)
        (202) 991 9534
        Alex 5553920011
        1 (800) 233-2010
        1.299.339.1020";

        let re = Regex::new(
            r#"(?x)
              (?:\+?1)?                       # Country Code Optional
              [\s\.]?
              (([2-9]\d{2})|\(([2-9]\d{2})\)) # Area Code
              [\s\.\-]?
              ([2-9]\d{2})                    # Exchange Code
              [\s\.\-]?
              (\d{4})                         # Subscriber Number"#,
        )?;

        let phone_numbers = re.captures_iter(phone_text).filter_map(|cap| {
            let groups = (cap.get(2).or(cap.get(3)), cap.get(4), cap.get(5));
            match groups {
                (Some(area), Some(ext), Some(sub)) => Some(PhoneNumber {
                    area: area.as_str(),
                    exchange: ext.as_str(),
                    subscriber: sub.as_str(),
                }),
                _ => None,
            }
        });

        assert_eq!(
            phone_numbers.map(|m| m.to_string()).collect::<Vec<_>>(),
            vec![
                "1 (505) 881-9292",
                "1 (505) 778-2212",
                "1 (505) 881-9297",
                "1 (202) 991-9534",
                "1 (555) 392-0011",
                "1 (800) 233-2010",
                "1 (299) 339-1020",
            ]
        );

        Ok(())
    }

### 通过多个正则来过滤日志文件

例子的目标是过滤出包含 "version X.X.X"、以 443 结尾的 IP 地址和特别的警告的日志行。

值得注意的是，由于在正则中反斜杠非常常见，因此使用 `r#""` 形式的原生字符串对于开发者和使用者都更加友好。

    use error_chain::error_chain;

    use std::fs::File;
    use std::io::{BufReader, BufRead};
    use regex::RegexSetBuilder;

    error_chain! {
       foreign_links {
           Io(std::io::Error);
           Regex(regex::Error);
       }
    }

    fn main() -> Result<()> {
        let log_path = "application.log";
        let buffered = BufReader::new(File::open(log_path)?);

        let set = RegexSetBuilder::new(&[
            r#"version "\d\.\d\.\d""#,
            r#"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:443"#,
            r#"warning.*timeout expired"#,
        ]).case_insensitive(true)
            .build()?;

        buffered
            .lines()
            .filter_map(|line| line.ok())
            .filter(|line| set.is_match(line.as_str()))
            .for_each(|x| println!("{}", x));

        Ok(())
    }

### 将文本中所有的指定模式替换成另外一种模式

下面代码将标准的 ISO 8601 YYYY-MM-DD 日期模式替换成带有斜杠的美式英语日期。例如 `2013-01-15` -> `01/15/2013`。

    use lazy_static::lazy_static;

    use std::borrow::Cow;
    use regex::Regex;

    fn reformat_dates(before: &str) -> Cow<str> {
        lazy_static! {
            static ref ISO8601_DATE_REGEX : Regex = Regex::new(
                r"(?P<y>\d{4})-(?P<m>\d{2})-(?P<d>\d{2})"
                ).unwrap();
        }
        ISO8601_DATE_REGEX.replace_all(before, "$m/$d/$y")
    }

    fn main() {
        let before = "2012-03-14, 2013-01-15 and 2014-07-05";
        let after = reformat_dates(before);
        assert_eq!(after, "03/14/2012, 01/15/2013 and 07/05/2014");
    }