about summary refs log tree commit diff
path: root/src/libregex/re.rs
diff options
context:
space:
mode:
authorEric Kidd <git@randomhacks.net>2014-12-13 13:33:18 -0500
committerEric Kidd <git@randomhacks.net>2014-12-14 08:56:51 -0500
commitc2b0d7dd8818a0dca9b1fa7af6873375907f05ca (patch)
tree369f80679f73646e73ed5f58b2041eb8183aa242 /src/libregex/re.rs
parent444fa1b7cffcd99ca5b8abb51acf979f06a25899 (diff)
downloadrust-c2b0d7dd8818a0dca9b1fa7af6873375907f05ca.tar.gz
rust-c2b0d7dd8818a0dca9b1fa7af6873375907f05ca.zip
Modify `regex::Captures::{at,name}` to return `Option`
Closes #14602.  As discussed in that issue, the existing `at` and `name`
functions represent two different results with the empty string:

1. Matched the empty string.
2. Did not match anything.

Consider the following example.  This regex has two named matched
groups, `key` and `value`. `value` is optional:

```rust
// Matches "foo", "foo;v=bar" and "foo;v=".
regex!(r"(?P<key>[a-z]+)(;v=(?P<value>[a-z]*))?");
```

We can access `value` using `caps.name("value")`, but there's no way for
us to distinguish between the `"foo"` and `"foo;v="` cases.

Early this year, @BurntSushi recommended modifying the existing `at` and
`name` functions to return `Option`, instead of adding new functions to
the API.

This is a [breaking-change], but the fix is easy:

- `refs.at(1)` becomes `refs.at(1).unwrap_or("")`.
- `refs.name(name)` becomes `refs.name(name).unwrap_or("")`.
Diffstat (limited to 'src/libregex/re.rs')
-rw-r--r--src/libregex/re.rs49
1 files changed, 24 insertions, 25 deletions
diff --git a/src/libregex/re.rs b/src/libregex/re.rs
index 1504e191985..53181bfbb7e 100644
--- a/src/libregex/re.rs
+++ b/src/libregex/re.rs
@@ -273,9 +273,9 @@ impl Regex {
     /// let re = regex!(r"'([^']+)'\s+\((\d{4})\)");
     /// let text = "Not my favorite movie: 'Citizen Kane' (1941).";
     /// let caps = re.captures(text).unwrap();
-    /// assert_eq!(caps.at(1), "Citizen Kane");
-    /// assert_eq!(caps.at(2), "1941");
-    /// assert_eq!(caps.at(0), "'Citizen Kane' (1941)");
+    /// assert_eq!(caps.at(1), Some("Citizen Kane"));
+    /// assert_eq!(caps.at(2), Some("1941"));
+    /// assert_eq!(caps.at(0), Some("'Citizen Kane' (1941)"));
     /// # }
     /// ```
     ///
@@ -291,9 +291,9 @@ impl Regex {
     /// let re = regex!(r"'(?P<title>[^']+)'\s+\((?P<year>\d{4})\)");
     /// let text = "Not my favorite movie: 'Citizen Kane' (1941).";
     /// let caps = re.captures(text).unwrap();
-    /// assert_eq!(caps.name("title"), "Citizen Kane");
-    /// assert_eq!(caps.name("year"), "1941");
-    /// assert_eq!(caps.at(0), "'Citizen Kane' (1941)");
+    /// assert_eq!(caps.name("title"), Some("Citizen Kane"));
+    /// assert_eq!(caps.name("year"), Some("1941"));
+    /// assert_eq!(caps.at(0), Some("'Citizen Kane' (1941)"));
     /// # }
     /// ```
     ///
@@ -434,7 +434,7 @@ impl Regex {
     /// # use regex::Captures; fn main() {
     /// let re = regex!(r"([^,\s]+),\s+(\S+)");
     /// let result = re.replace("Springsteen, Bruce", |&: caps: &Captures| {
-    ///     format!("{} {}", caps.at(2), caps.at(1))
+    ///     format!("{} {}", caps.at(2).unwrap_or(""), caps.at(1).unwrap_or(""))
     /// });
     /// assert_eq!(result.as_slice(), "Bruce Springsteen");
     /// # }
@@ -712,27 +712,25 @@ impl<'t> Captures<'t> {
         Some((self.locs[s].unwrap(), self.locs[e].unwrap()))
     }
 
-    /// Returns the matched string for the capture group `i`.
-    /// If `i` isn't a valid capture group or didn't match anything, then the
-    /// empty string is returned.
-    pub fn at(&self, i: uint) -> &'t str {
+    /// Returns the matched string for the capture group `i`.  If `i` isn't
+    /// a valid capture group or didn't match anything, then `None` is
+    /// returned.
+    pub fn at(&self, i: uint) -> Option<&'t str> {
         match self.pos(i) {
-            None => "",
-            Some((s, e)) => {
-                self.text.slice(s, e)
-            }
+            None => None,
+            Some((s, e)) => Some(self.text.slice(s, e))
         }
     }
 
-    /// Returns the matched string for the capture group named `name`.
-    /// If `name` isn't a valid capture group or didn't match anything, then
-    /// the empty string is returned.
-    pub fn name(&self, name: &str) -> &'t str {
+    /// Returns the matched string for the capture group named `name`.  If
+    /// `name` isn't a valid capture group or didn't match anything, then
+    /// `None` is returned.
+    pub fn name(&self, name: &str) -> Option<&'t str> {
         match self.named {
-            None => "",
+            None => None,
             Some(ref h) => {
                 match h.get(name) {
-                    None => "",
+                    None => None,
                     Some(i) => self.at(*i),
                 }
             }
@@ -769,11 +767,12 @@ impl<'t> Captures<'t> {
         // FIXME: Don't use regexes for this. It's completely unnecessary.
         let re = Regex::new(r"(^|[^$]|\b)\$(\w+)").unwrap();
         let text = re.replace_all(text, |&mut: refs: &Captures| -> String {
-            let (pre, name) = (refs.at(1), refs.at(2));
+            let pre = refs.at(1).unwrap_or("");
+            let name = refs.at(2).unwrap_or("");
             format!("{}{}", pre,
                     match from_str::<uint>(name.as_slice()) {
-                None => self.name(name).to_string(),
-                Some(i) => self.at(i).to_string(),
+                None => self.name(name).unwrap_or("").to_string(),
+                Some(i) => self.at(i).unwrap_or("").to_string(),
             })
         });
         let re = Regex::new(r"\$\$").unwrap();
@@ -802,7 +801,7 @@ impl<'t> Iterator<&'t str> for SubCaptures<'t> {
     fn next(&mut self) -> Option<&'t str> {
         if self.idx < self.caps.len() {
             self.idx += 1;
-            Some(self.caps.at(self.idx - 1))
+            Some(self.caps.at(self.idx - 1).unwrap_or(""))
         } else {
             None
         }