Repository: fancy-regex/fancy-regex (language: rust)

Issue:
split function missing
i wanted to split a string with regex and was wondering if theres a reason that that function is missing? its implemented in the rust-regex crate and also their regex implements pattern so the std::split is an option as well. 
i am interested in adding it but wondering if it was purposely left out for some reason? 


Discussion / hints:
I believe it has only been left out because nobody has needed it yet and thus nobody has implemented it yet. There have been some issues reported in the past where people asked for `fancy-regex` to implement some APIs that `regex` has and to my knowledge they have never been rejected.
It would be an awesome feature. 
definitely needed
I'm also missing this function.  I whipped up the following to satisfy my need, thought I'd share here in case someone has the bandwidth to make a proper PR out of it.
The logic here is a interpretation of https://github.com/rust-lang/regex/blob/8856fe36ac7dc37989e6ffb26b5fc57189bae626/regex-automata/src/meta/regex.rs#L2180-L2219 but adapted to work on the public types available from this crate.

```rust

struct Split<'r, 'h> {
    finder: Matches<'r, 'h>,
    last: usize,
    haystack: &'h str,
}

impl<'r, 'h> Split<'r, 'h> {
    fn split(re: &'r Regex, haystack: &'h str) -> Self {
        Self {
            finder: re.find_iter(haystack),
            last: 0,
            haystack,
        }
    }
}

impl<'r, 'h> Iterator for Split<'r, 'h> {
    type Item = Result<&'h str, fancy_regex::Error>;

    fn next(&mut self) -> Option<Result<&'h str, fancy_regex::Error>> {
        match self.finder.next() {
            None => {
                let len = self.haystack.len();
                if self.last > len {
                    None
                } else {
                    let span = &self.haystack[self.last..len];
                    self.last = len + 1; // Next call will return None
                    Some(Ok(span))
                }
            }
            Some(Ok(m)) => {
                let span = &self.haystack[self.last..m.start()];
                self.last = m.end();
                Some(Ok(span))
            }
            Some(Err(e)) => Some(Err(e)),
        }
    }
}

fn split_into_vec(re: &Regex, haystack: &str) -> Result<Vec<String>, fancy_regex::Error> {
    let mut result = vec![];
    for m in Split::split(re, haystack) {
        let m = m?;
        result.push(m.to_string());
    }
    Ok(result)
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn fancy_split() {
        let re = Regex::new("[ \t]+").unwrap();
        let hay = "a b \t  c\td    e";
        let fields = split_into_vec(&re, hay).unwrap();
        assert_eq!(fields, vec!["a", "b", "c", "d", "e"]);
    }
}

```

Fix patch:
diff --git a/src/lib.rs b/src/lib.rs
index eb7b049..13f5afa 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -86,6 +86,17 @@ let group = captures.get(1).expect("No group");
 assert_eq!(group.as_str(), "20");
 ```
 
+## Example: Splitting text
+
+```rust
+use fancy_regex::Regex;
+
+let re = Regex::new(r"[ \t]+").unwrap();
+let target = "a b \t  c\td    e";
+let fields: Vec<&str> = re.split(target).map(|x| x.unwrap()).collect();
+assert_eq!(fields, vec!["a", "b", "c", "d", "e"]);
+```
+
 # Syntax
 
 The regex syntax is based on the [regex] crate's, with some additional supported syntax.
@@ -392,6 +403,58 @@ pub struct SubCaptureMatches<'c, 't> {
     i: usize,
 }
 
+#[derive(Debug)]
+/// An iterator over all substrings delimited by a regex.
+///
+/// This iterator yields `Result<&'h str>`, where each item is a substring of the
+/// target string that is delimited by matches of the regular expression. It stops when there
+/// are no more substrings to yield.
+///
+/// `'r` is the lifetime of the compiled regular expression, and `'h` is the
+/// lifetime of the target string being split.
+///
+/// This iterator can be created via the [`Regex::split`] method.
+pub struct Split<'r, 'h> {
+    matches: Matches<'r, 'h>,
+    next_start: usize,
+    target: &'h str,
+}
+
+impl<'r, 'h> Iterator for Split<'r, 'h> {
+    type Item = Result<&'h str>;
+
+    /// Returns the next substring that results from splitting the target string by the regex.
+    ///
+    /// If no more matches are found, returns the remaining part of the string,
+    /// or `None` if all substrings have been yielded.
+    fn next(&mut self) -> Option<Result<&'h str>> {
+        match self.matches.next() {
+            None => {
+                let len = self.target.len();
+                if self.next_start > len {
+                    // No more substrings to return
+                    None
+                } else {
+                    // Return the last part of the target string
+                    // Next call will return None
+                    let part = &self.target[self.next_start..len];
+                    self.next_start = len + 1;
+                    Some(Ok(part))
+                }
+            }
+            // Return the next substring
+            Some(Ok(m)) => {
+                let part = &self.target[self.next_start..m.start()];
+                self.next_start = m.end();
+                Some(Ok(part))
+            }
+            Some(Err(e)) => Some(Err(e)),
+        }
+    }
+}
+
+impl<'r, 'h> core::iter::FusedIterator for Split<'r, 'h> {}
+
 #[derive(Clone, Debug)]
 struct RegexOptions {
     pattern: String,
@@ -1002,6 +1065,30 @@ impl Regex {
         new.push_str(&text[last_match..]);
         Ok(Cow::Owned(new))
     }
+
+    /// Splits the string by matches of the regex.
+    ///
+    /// Returns an iterator over the substrings of the target string
+    ///  that *aren't* matched by the regex.
+    ///
+    /// # Example
+    ///
+    /// To split a string delimited by arbitrary amounts of spaces or tabs:
+    ///
+    /// ```rust
+    /// # use fancy_regex::Regex;
+    /// let re = Regex::new(r"[ \t]+").unwrap();
+    /// let target = "a b \t  c\td    e";
+    /// let fields: Vec<&str> = re.split(target).map(|x| x.unwrap()).collect();
+    /// assert_eq!(fields, vec!["a", "b", "c", "d", "e"]);
+    /// ```
+    pub fn split<'r, 'h>(&'r self, target: &'h str) -> Split<'r, 'h> {
+        Split {
+            matches: self.find_iter(target),
+            next_start: 0,
+            target,
+        }
+    }
 }
 
 impl TryFrom<&str> for Regex {


Test patch:
diff --git a/tests/splitting.rs b/tests/splitting.rs
new file mode 100644
index 0000000..60b154f
--- /dev/null
+++ b/tests/splitting.rs
@@ -0,0 +1,93 @@
+use fancy_regex::Regex;
+
+fn split_to_vec<'a>(re_str: &'a str, target: &'a str) -> Vec<&'a str> {
+    let re = Regex::new(re_str).unwrap();
+    re.split(target).map(|x| x.unwrap()).collect()
+}
+
+#[test]
+fn split_left() {
+    let result: Vec<&str> = split_to_vec("1", "123");
+    assert_eq!(result, vec!["", "23"]);
+}
+
+#[test]
+fn split_center() {
+    let result: Vec<&str> = split_to_vec("2", "123");
+    assert_eq!(result, vec!["1", "3"]);
+}
+
+#[test]
+fn split_right() {
+    let result: Vec<&str> = split_to_vec("3", "123");
+    assert_eq!(result, vec!["12", ""]);
+}
+
+#[test]
+fn split_no_matches() {
+    let result: Vec<&str> = split_to_vec("4", "123");
+    assert_eq!(result, vec!["123"]);
+}
+
+#[test]
+fn split_empty() {
+    let result: Vec<&str> = split_to_vec("1", "");
+    assert_eq!(result, vec![""]);
+}
+
+#[test]
+fn split_by_empty() {
+    let result: Vec<&str> = split_to_vec("", "123");
+    assert_eq!(result, vec!["", "1", "2", "3", ""]);
+}
+
+#[test]
+fn split_by_own() {
+    let result: Vec<&str> = split_to_vec("123", "123");
+    assert_eq!(result, vec!["", ""]);
+}
+
+#[test]
+fn split_consecutive_matches() {
+    let result: Vec<&str> = split_to_vec("1", "111");
+    assert_eq!(result, vec!["", "", "", ""]);
+}
+
+#[test]
+fn split_by_substring() {
+    let result: Vec<&str> = split_to_vec("123", "123456");
+    assert_eq!(result, vec!["", "456"]);
+
+    let result: Vec<&str> = split_to_vec("234|678", "123456789");
+    assert_eq!(result, vec!["1", "5", "9"]);
+}
+
+#[test]
+fn split_multiple_different_characters() {
+    let result: Vec<&str> = split_to_vec("[1-3]", "123456");
+    assert_eq!(result, vec!["", "", "", "456"]);
+}
+
+#[test]
+fn split_mixed_characters() {
+    let result: Vec<&str> = split_to_vec("[236]", "123456");
+    assert_eq!(result, vec!["1", "", "45", ""]);
+}
+
+#[test]
+fn split_with_backreferences() {
+    let result: Vec<&str> = split_to_vec(r"(1|2)\1", "12112122");
+    assert_eq!(result, vec!["12", "21", ""]);
+}
+
+#[test]
+fn split_with_look_around() {
+    let result: Vec<&str> = split_to_vec(r"(?<=1)2", "12112122");
+    assert_eq!(result, vec!["1", "11", "1", "2"]);
+
+    let result: Vec<&str> = split_to_vec(r"1(?=2)", "12112122");
+    assert_eq!(result, vec!["", "21", "2", "22"]);
+
+    let result: Vec<&str> = split_to_vec(r"(?<=2)1(?=2)", "12112122");
+    assert_eq!(result, vec!["12112", "22"]);
+}