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("")`.
This commit is contained in:
Eric Kidd 2014-12-13 13:33:18 -05:00
parent 444fa1b7cf
commit c2b0d7dd88
5 changed files with 38 additions and 37 deletions

View File

@ -393,7 +393,7 @@ fn extract_gdb_version(full_version_line: Option<String>) -> Option<String> {
match re.captures(full_version_line) {
Some(captures) => {
Some(captures.at(2).to_string())
Some(captures.at(2).unwrap_or("").to_string())
}
None => {
println!("Could not extract GDB version from line '{}'",
@ -427,7 +427,7 @@ fn extract_lldb_version(full_version_line: Option<String>) -> Option<String> {
match re.captures(full_version_line) {
Some(captures) => {
Some(captures.at(1).to_string())
Some(captures.at(1).unwrap_or("").to_string())
}
None => {
println!("Could not extract LLDB version from line '{}'",

View File

@ -66,10 +66,10 @@ fn parse_expected(last_nonfollow_error: Option<uint>,
line: &str,
re: &Regex) -> Option<(WhichLine, ExpectedError)> {
re.captures(line).and_then(|caps| {
let adjusts = caps.name("adjusts").len();
let kind = caps.name("kind").to_ascii_lower();
let msg = caps.name("msg").trim().to_string();
let follow = caps.name("follow").len() > 0;
let adjusts = caps.name("adjusts").unwrap_or("").len();
let kind = caps.name("kind").unwrap_or("").to_ascii_lower();
let msg = caps.name("msg").unwrap_or("").trim().to_string();
let follow = caps.name("follow").unwrap_or("").len() > 0;
let (which, line) = if follow {
assert!(adjusts == 0, "use either //~| or //~^, not both.");

View File

@ -173,10 +173,10 @@ fn parse_antlr_token(s: &str, tokens: &HashMap<String, token::Token>) -> TokenAn
);
let m = re.captures(s).expect(format!("The regex didn't match {}", s).as_slice());
let start = m.name("start");
let end = m.name("end");
let toknum = m.name("toknum");
let content = m.name("content");
let start = m.name("start").unwrap_or("");
let end = m.name("end").unwrap_or("");
let toknum = m.name("toknum").unwrap_or("");
let content = m.name("content").unwrap_or("");
let proto_tok = tokens.get(toknum).expect(format!("didn't find token {} in the map",
toknum).as_slice());

View File

@ -103,7 +103,9 @@
//! let re = regex!(r"(\d{4})-(\d{2})-(\d{2})");
//! let text = "2012-03-14, 2013-01-01 and 2014-07-05";
//! for cap in re.captures_iter(text) {
//! println!("Month: {} Day: {} Year: {}", cap.at(2), cap.at(3), cap.at(1));
//! println!("Month: {} Day: {} Year: {}",
//! cap.at(2).unwrap_or(""), cap.at(3).unwrap_or(""),
//! cap.at(1).unwrap_or(""));
//! }
//! // Output:
//! // Month: 03 Day: 14 Year: 2012
@ -285,7 +287,7 @@
//! # fn main() {
//! let re = regex!(r"(?i)a+(?-i)b+");
//! let cap = re.captures("AaAaAbbBBBb").unwrap();
//! assert_eq!(cap.at(0), "AaAaAbb");
//! assert_eq!(cap.at(0), Some("AaAaAbb"));
//! # }
//! ```
//!

View File

@ -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
}