<commit_before>/*****************************************************************************
 * ------------------------------------------------------------------------- *
 * Licensed under the Apache License, Version 2.0 (the "License");           *
 * you may not use this file except in compliance with the License.          *
 * You may obtain a copy of the License at                                   *
 *                                                                           *
 * http://www.apache.org/licenses/LICENSE-2.0                                *
 *                                                                           *
 * Unless required by applicable law or agreed to in writing, software       *
 * distributed under the License is distributed on an "AS IS" BASIS,         *
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  *
 * See the License for the specific language governing permissions and       *
 * limitations under the License.                                            *
 *****************************************************************************/
package com.google.mu.util;

import static java.util.Objects.requireNonNull;

import java.util.Objects;
import java.util.Optional;
import java.util.function.UnaryOperator;
import java.util.stream.Collector;
import java.util.stream.Collectors;

import com.google.mu.util.stream.BiCollector;
import com.google.mu.util.stream.BiCollectors;

/**
 * Utilities for creating patterns that attempt to match a substring in an input string. The matched
 * substring can be {@link Pattern#from extracted}, {@link Pattern#removeFrom removed}, {@link
 * Pattern#replaceFrom replaced}, or used to divide the input string into parts.
 *
 * <p>For example, to strip off the "http://" prefix from a uri string if present:
 *
 * <pre>
 *   static String stripHttp(String uri) {
 *     return Substring.prefix("http://").removeFrom(uri);
 *   }
 * </pre>
 *
 * To strip off either an "http://" or "https://" prefix if present:
 *
 * <pre>
 *   static import com.google.common.labs.base.Substring.prefix;
 *
 *   static String stripHttpOrHttps(String uri) {
 *     return prefix("http://").or(prefix("https://")).removeFrom(uri);
 *   }
 * </pre>
 *
 * To strip off a suffix starting with a dash (-) character:
 *
 * <pre>
 *   static String stripDashSuffix(String str) {
 *     return last('-').toEnd().removeFrom(str);
 *   }
 * </pre>
 *
 * To replace a trailing "//" with "/":
 *
 * <pre>
 *   static String fixTrailingSlash(String str) {
 *     return Substring.suffix("//").replaceFrom(str, '/');
 *   }
 * </pre>
 *
 * To extract the 'name' and 'value' from an input string in the format of "name:value":
 *
 * <pre>
 *   String str = ...;
 *   Substring.Match colon = Substring.first(':').in(str).orElseThrow(BadFormatException::new);
 *   String name = colon.before();
 *   String value = colon.after();
 * </pre>
 *
 * To split a stream of strings into key-value pairs:
 *
 * <pre>{@code
 *   ImmutableListMultimap<String, String> tags = lines.stream()
 *       .collect(first(':').splittingTrimmed(ImmutableListMultimap::toImmutableListMultimap));
 * }</pre>
 *
 * @since 2.0
 */
public final class Substring {

  /** {@code Pattern} that never matches any substring. */
  public static final Pattern NONE = new Pattern() {
    @Override public Match match(String s) {
      requireNonNull(s);
      return null;
    }
  };

  /**
   * {@code Pattern} that matches the empty substring at the beginning of the input string.
   *
   * @since 2.1
   */
  public static final Pattern BEGINNING = new Pattern() {
    @Override public Match match(String s) {
      requireNonNull(s);
      return new Match(s, 0, 0);
    }
  };

  /**
   * {@code Pattern} that matches the empty substring at the end of the input string.
   *
   * @since 2.1
   */
  public static final Pattern END = new Pattern() {
    @Override public Match match(String s) {
      return new Match(s, s.length(), s.length());
    }
  };

  /** Returns a {@code Pattern} that matches strings starting with {@code prefix}. */
  public static Pattern prefix(String prefix) {
    requireNonNull(prefix);
    return new Pattern() {
      @Override Match match(String input) {
        return input.startsWith(prefix) ? new Match(input, 0, prefix.length()) : null;
      }
    };
  }

  /** Returns a {@code Pattern} that matches strings starting with {@code prefix}. */
  public static Pattern prefix(char prefix) {
    return prefix(Character.toString(prefix));
  }

  /** Returns a {@code Pattern} that matches strings ending with {@code suffix}. */
  public static Pattern suffix(String suffix) {
    requireNonNull(suffix);
    return new Pattern() {
      @Override Match match(String input) {
        return input.endsWith(suffix)
            ? new Match(input, input.length() - suffix.length(), input.length())
            : null;
      }
    };
  }

  /** Returns a {@code Pattern} that matches strings ending with {@code suffix}. */
  public static Pattern suffix(char suffix) {
    return suffix(Character.toString(suffix));
  }

  /** Returns a {@code Pattern} that matches the first occurrence of {@code c}. */
  public static Pattern first(char c) {
    return new Pattern() {
      @Override Match match(String input) {
        return matchIfValidIndex(input, input.indexOf(c), 1);
      }
    };
  }

  /** Returns a {@code Pattern} that matches the first occurrence of {@code str}. */
  public static Pattern first(String str) {
    requireNonNull(str);
    return new Pattern() {
      @Override Match match(String input) {
        return matchIfValidIndex(input, input.indexOf(str), str.length());
      }
    };
  }

  /**
   * Returns a {@code Pattern} that matches the first occurrence of {@code regexPattern}.
   *
   * <p>Unlike {@code str.replaceFirst(regexPattern, replacement)},
   * <pre>first(regexPattern).replaceFrom(str, replacement)</pre> treats the {@code replacement} as a literal
   * string with no special handling of backslash (\) and dollar sign ($) characters.
   *
   * @since 2.2
   */
  public static Pattern first(java.util.regex.Pattern regexPattern) {
    return first(regexPattern, 0);
  }

  /**
   * Returns a {@code Pattern} that matches capturing {@code group} of {@code regexPattern}.
   *
   * @throws IndexOutOfBoundsException if {@code group} is negative or exceeds the number of
   *         capturing groups in {@code regexPattern}.
   *
   * @since 2.2
   */
  public static Pattern first(java.util.regex.Pattern regexPattern, int group) {
    requireNonNull(regexPattern);
    if (group < 0 || group > 0 && group > regexPattern.matcher("").groupCount()) {
      throw new IndexOutOfBoundsException("Capturing group " + group + " doesn't exist.");
    }
    return new Pattern() {
      @Override Match match(String input) {
        java.util.regex.Matcher matcher = regexPattern.matcher(input);
        return matcher.find() ? new Match(input, matcher.start(group), matcher.end(group)) : null;
      }
    };
  }

  /** Returns a {@code Pattern} that matches the last occurrence of {@code c}. */
  public static Pattern last(char c) {
    return new Pattern() {
      @Override Match match(String input) {
        return matchIfValidIndex(input, input.lastIndexOf(c), 1);
      }
    };
  }

  /** Returns a {@code Pattern} that matches the last occurrence of {@code str}. */
  public static Pattern last(String str) {
    requireNonNull(str);
    return new Pattern() {
      @Override Match match(String input) {
        return matchIfValidIndex(input, input.lastIndexOf(str), str.length());
      }
    };
  }

  /**
   * Returns a {@code Pattern} that will match from the beginning of the original string up to
   * the substring matched by {@code pattern} <em>inclusively</em>. For example:
   *
   * <pre>
   *   String uri = "http://google.com";
   *   String schemeStripped = upToIncluding(first("://")).removeFrom(uri);
   *   assertThat(schemeStripped).isEqualTo("google.com");
   * </pre>
   *
   * <p>To match from the start of {@code pattern} to the end of the original string, use
   * {@link Pattern#toEnd} instead.
   *
   * @since 2.2
   */
  public static Pattern upToIncluding(Pattern endingPoint) {
    return endingPoint.map(Match::fromBeginning);
  }

  /**
   * Returns a {@code Pattern} that will match the substring between {@code open} and {@code close}.
   * For example the following pattern finds the link text in markdown syntax:
   * <pre>
   *   private static final Substring.Pattern DEPOT_PATH =
   *       Substring.between(first("//depot/"), last('/'));
   *   String path = DEPOT_PATH.from("//depot/google3/foo/bar/baz.txt").get();
   *   assertThat(path).isEqualTo("google3/foo/bar");
   * </pre>
   *
   * <p>Note that the {@code close} pattern is matched against the substring after the end of
   * {@code open}. For example: <pre>
   *   String dir1 = Substring.between(first('/'), first('/')).from("/usr/home/abc").get();
   *   assertThat(dir1).isEqualTo("usr");
   * </pre>
   *
   * @since 2.1
   */
  public static Pattern before(Pattern delimiter) {
    return delimiter.map(Match::preceding);
  }

  /**
   * Returns a {@code Pattern} that covers the substring after {@code delimiter}. For example:
   *
   * <pre>
   *   String file = "/home/path/file.txt";
   *   String ext = Substring.after(last('.')).from(file).orElseThrow(...);
   *   assertThat(ext).isEqualTo("txt");
   * </pre>
   *
   * @since 2.1
   */
  public static Pattern after(Pattern delimiter) {
    return delimiter.map(Match::following);
  }

  /**
   * Returns a {@code Pattern} that will match the substring between {@code open} and {@code close}.
   * For example: <pre>
   *   String quoted = Substring.between(first('('), first(')'))
   *       .from(input)
   *       .orElseThrow(...);
   * </pre>
   *
   * @since 2.1
   */
  public static Pattern between(Pattern open, Pattern close) {
    requireNonNull(open);
    requireNonNull(close);
    return new Pattern() {
      @Override Match match(String input) {
        Match left = open.match(input);
        if (left == null) return null;
        Match right = close.match(input.substring(left.endIndex));
        if (right == null) return null;
        return new Match(input, left.endIndex, left.endIndex + right.startIndex);
      }
    };
  }

  /** A substring pattern that can be matched against a string to find substrings. */
  public static abstract class Pattern {
    /** Matches against {@code string} and returns null if not found. */
    abstract Match match(String input);

    /**
     * Matches this pattern against {@code string}, returning a {@code Match} if successful, or
     * {@code empty()} otherwise.
     *
     * <p>This is useful if you need to call {@link Match} methods, like {@link Match#remove} or
     * {@link Match#before}. If you just need the matched substring itself, prefer to use
     * {@link #from} instead.
     */
    public final Optional<Match> in(String string) {
      return Optional.ofNullable(match(string));
    }

    /**
     * Matches this pattern against {@code string}, returning the matched substring if successful,
     * or {@code empty()} otherwise. {@code pattern.from(str)} is equivalent to
     * {@code pattern.in(str).map(Object::toString)}.
     *
     * <p>This is useful if you only need the matched substring itself. Use {@link #in} if you need
     * to call {@link Match} methods, like {@link Match#remove} or {@link Match#before}.
     *
     * @since 2.1
     */
    public final Optional<String> from(String string) {
      return Optional.ofNullable(Objects.toString(match(string), null));
    }

    /**
     * Matches this pattern against {@code string}, and returns a copy with the matched substring
     * removed if successful. Otherwise, returns {@code string} unchanged.
     */
    public final String removeFrom(String string) {
      Match match = match(string);
      return match == null ? string : match.remove();
    }

    /**
     * Returns a new string with the substring matched by {@code this} replaced by {@code
     * replacement}. Returns {@code string} as-is if a substring is not found.
     */
    public final String replaceFrom(String string, CharSequence replacement) {
      requireNonNull(replacement);
      Match match = match(string);
      return match == null ? string : match.replaceWith(replacement);
    }

    /**
     * Returns a {@code Pattern} that fall backs to using {@code that} if {@code this} fails to
     * match.
     */
    public final Pattern or(Pattern that) {
      requireNonNull(that);
      Pattern base = this;
      return new Pattern() {
        @Override Match match(String input) {
          Match match = base.match(input);
          return match == null ? that.match(input) : match;
        }
      };
    }

    /**
     * Returns a {@code Pattern} that will match from the substring matched by {@code this} to the
     * end of the input string. For example:
     *
     * <pre>
     *   String line = "return foo; // some comment...";
     *   String commentRemoved = first("//").toEnd().removeFrom(line).trim();
     *   assertThat(commentRemoved).isEqualTo("return foo;");
     * </pre>
     *
     * <p>To match from the beginning of the input string to the end of a pattern, use
     * {@link Substring#upToIncluding} instead.
     *
     * @since 2.2
     */
    public final Pattern toEnd() {
      return map(Match::toEnd);
    }

    /**
     * Returns a {@code Collector} that splits each string in a stream into two parts, with the
     * matched substring as the separator. For example:
     *
     * <pre>{@code
     * ImmutableSetMultimap<String, String> keyValues =
     *     readLines(file, UTF_8).stream()
     *         .collect(first(':').splitting(ImmutableSetMultimap::toImmutableSetMultimap));
     * }</pre>
     *
     * <p>If you need to trim the key-value pairs, you can use {@link #splittingTrimmed
     * splittingTrimmed()}. For more flexible use cases, such as transforming the keys or values
     * to another type, one approach is to collect the pairs into a {@code BiStream} first and
     * then chain away:
     *
     * <pre>{@code
     * ImmutableSetMultimap<Key, Value> keyValues =
     *     readLines(file, UTF_8).stream()
     *         .collect(first(':').splitting(BiStream::toBiStream))
     *         .mapKeys(Key::of)
     *         .mapValues(Value::of)
     *         .collect(ImmutableSetMultimap::toImmutableSetMultimap);
     * }</pre>
     * 
     * Or, use the {@link #in} method to operate on the {@link Match} objects, which also allows
     * custom handling of the error of not finding the separator, as well as the ability to report
     * the offending line number:
     *
     * <pre>{@code
     * ImmutableSetMultimap<Key, Value> keyValues =
     *     BiStream.zip(MoreStreams.indexFrom(1), readLines(file, UTF_8))
     *         .mapValues(first(':')::in)
     *         .mapValues((line, m) -><commit_msg>document splitting() using toBiStream() and also zip(indexFrom(1))<commit_after>/*****************************************************************************
 * ------------------------------------------------------------------------- *
 * Licensed under the Apache License, Version 2.0 (the "License");           *
 * you may not use this file except in compliance with the License.          *
 * You may obtain a copy of the License at                                   *
 *                                                                           *
 * http://www.apache.org/licenses/LICENSE-2.0                                *
 *                                                                           *
 * Unless required by applicable law or agreed to in writing, software       *
 * distributed under the License is distributed on an "AS IS" BASIS,         *
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  *
 * See the License for the specific language governing permissions and       *
 * limitations under the License.                                            *
 *****************************************************************************/
package com.google.mu.util;

import static java.util.Objects.requireNonNull;

import java.util.Objects;
import java.util.Optional;
import java.util.function.UnaryOperator;
import java.util.stream.Collector;
import java.util.stream.Collectors;

import com.google.mu.util.stream.BiCollector;
import com.google.mu.util.stream.BiCollectors;

/**
 * Utilities for creating patterns that attempt to match a substring in an input string. The matched
 * substring can be {@link Pattern#from extracted}, {@link Pattern#removeFrom removed}, {@link
 * Pattern#replaceFrom replaced}, or used to divide the input string into parts.
 *
 * <p>For example, to strip off the "http://" prefix from a uri string if present:
 *
 * <pre>
 *   static String stripHttp(String uri) {
 *     return Substring.prefix("http://").removeFrom(uri);
 *   }
 * </pre>
 *
 * To strip off either an "http://" or "https://" prefix if present:
 *
 * <pre>
 *   static import com.google.common.labs.base.Substring.prefix;
 *
 *   static String stripHttpOrHttps(String uri) {
 *     return prefix("http://").or(prefix("https://")).removeFrom(uri);
 *   }
 * </pre>
 *
 * To strip off a suffix starting with a dash (-) character:
 *
 * <pre>
 *   static String stripDashSuffix(String str) {
 *     return last('-').toEnd().removeFrom(str);
 *   }
 * </pre>
 *
 * To replace a trailing "//" with "/":
 *
 * <pre>
 *   static String fixTrailingSlash(String str) {
 *     return Substring.suffix("//").replaceFrom(str, '/');
 *   }
 * </pre>
 *
 * To extract the 'name' and 'value' from an input string in the format of "name:value":
 *
 * <pre>
 *   String str = ...;
 *   Substring.Match colon = Substring.first(':').in(str).orElseThrow(BadFormatException::new);
 *   String name = colon.before();
 *   String value = colon.after();
 * </pre>
 *
 * To split a stream of strings into key-value pairs:
 *
 * <pre>{@code
 *   ImmutableListMultimap<String, String> tags = lines.stream()
 *       .collect(first(':').splittingTrimmed(ImmutableListMultimap::toImmutableListMultimap));
 * }</pre>
 *
 * @since 2.0
 */
public final class Substring {

  /** {@code Pattern} that never matches any substring. */
  public static final Pattern NONE = new Pattern() {
    @Override public Match match(String s) {
      requireNonNull(s);
      return null;
    }
  };

  /**
   * {@code Pattern} that matches the empty substring at the beginning of the input string.
   *
   * @since 2.1
   */
  public static final Pattern BEGINNING = new Pattern() {
    @Override public Match match(String s) {
      requireNonNull(s);
      return new Match(s, 0, 0);
    }
  };

  /**
   * {@code Pattern} that matches the empty substring at the end of the input string.
   *
   * @since 2.1
   */
  public static final Pattern END = new Pattern() {
    @Override public Match match(String s) {
      return new Match(s, s.length(), s.length());
    }
  };

  /** Returns a {@code Pattern} that matches strings starting with {@code prefix}. */
  public static Pattern prefix(String prefix) {
    requireNonNull(prefix);
    return new Pattern() {
      @Override Match match(String input) {
        return input.startsWith(prefix) ? new Match(input, 0, prefix.length()) : null;
      }
    };
  }

  /** Returns a {@code Pattern} that matches strings starting with {@code prefix}. */
  public static Pattern prefix(char prefix) {
    return prefix(Character.toString(prefix));
  }

  /** Returns a {@code Pattern} that matches strings ending with {@code suffix}. */
  public static Pattern suffix(String suffix) {
    requireNonNull(suffix);
    return new Pattern() {
      @Override Match match(String input) {
        return input.endsWith(suffix)
            ? new Match(input, input.length() - suffix.length(), input.length())
            : null;
      }
    };
  }

  /** Returns a {@code Pattern} that matches strings ending with {@code suffix}. */
  public static Pattern suffix(char suffix) {
    return suffix(Character.toString(suffix));
  }

  /** Returns a {@code Pattern} that matches the first occurrence of {@code c}. */
  public static Pattern first(char c) {
    return new Pattern() {
      @Override Match match(String input) {
        return matchIfValidIndex(input, input.indexOf(c), 1);
      }
    };
  }

  /** Returns a {@code Pattern} that matches the first occurrence of {@code str}. */
  public static Pattern first(String str) {
    requireNonNull(str);
    return new Pattern() {
      @Override Match match(String input) {
        return matchIfValidIndex(input, input.indexOf(str), str.length());
      }
    };
  }

  /**
   * Returns a {@code Pattern} that matches the first occurrence of {@code regexPattern}.
   *
   * <p>Unlike {@code str.replaceFirst(regexPattern, replacement)},
   * <pre>first(regexPattern).replaceFrom(str, replacement)</pre> treats the {@code replacement} as a literal
   * string with no special handling of backslash (\) and dollar sign ($) characters.
   *
   * @since 2.2
   */
  public static Pattern first(java.util.regex.Pattern regexPattern) {
    return first(regexPattern, 0);
  }

  /**
   * Returns a {@code Pattern} that matches capturing {@code group} of {@code regexPattern}.
   *
   * @throws IndexOutOfBoundsException if {@code group} is negative or exceeds the number of
   *         capturing groups in {@code regexPattern}.
   *
   * @since 2.2
   */
  public static Pattern first(java.util.regex.Pattern regexPattern, int group) {
    requireNonNull(regexPattern);
    if (group < 0 || group > 0 && group > regexPattern.matcher("").groupCount()) {
      throw new IndexOutOfBoundsException("Capturing group " + group + " doesn't exist.");
    }
    return new Pattern() {
      @Override Match match(String input) {
        java.util.regex.Matcher matcher = regexPattern.matcher(input);
        return matcher.find() ? new Match(input, matcher.start(group), matcher.end(group)) : null;
      }
    };
  }

  /** Returns a {@code Pattern} that matches the last occurrence of {@code c}. */
  public static Pattern last(char c) {
    return new Pattern() {
      @Override Match match(String input) {
        return matchIfValidIndex(input, input.lastIndexOf(c), 1);
      }
    };
  }

  /** Returns a {@code Pattern} that matches the last occurrence of {@code str}. */
  public static Pattern last(String str) {
    requireNonNull(str);
    return new Pattern() {
      @Override Match match(String input) {
        return matchIfValidIndex(input, input.lastIndexOf(str), str.length());
      }
    };
  }

  /**
   * Returns a {@code Pattern} that will match from the beginning of the original string up to
   * the substring matched by {@code pattern} <em>inclusively</em>. For example:
   *
   * <pre>
   *   String uri = "http://google.com";
   *   String schemeStripped = upToIncluding(first("://")).removeFrom(uri);
   *   assertThat(schemeStripped).isEqualTo("google.com");
   * </pre>
   *
   * <p>To match from the start of {@code pattern} to the end of the original string, use
   * {@link Pattern#toEnd} instead.
   *
   * @since 2.2
   */
  public static Pattern upToIncluding(Pattern endingPoint) {
    return endingPoint.map(Match::fromBeginning);
  }

  /**
   * Returns a {@code Pattern} that will match the substring between {@code open} and {@code close}.
   * For example the following pattern finds the link text in markdown syntax:
   * <pre>
   *   private static final Substring.Pattern DEPOT_PATH =
   *       Substring.between(first("//depot/"), last('/'));
   *   String path = DEPOT_PATH.from("//depot/google3/foo/bar/baz.txt").get();
   *   assertThat(path).isEqualTo("google3/foo/bar");
   * </pre>
   *
   * <p>Note that the {@code close} pattern is matched against the substring after the end of
   * {@code open}. For example: <pre>
   *   String dir1 = Substring.between(first('/'), first('/')).from("/usr/home/abc").get();
   *   assertThat(dir1).isEqualTo("usr");
   * </pre>
   *
   * @since 2.1
   */
  public static Pattern before(Pattern delimiter) {
    return delimiter.map(Match::preceding);
  }

  /**
   * Returns a {@code Pattern} that covers the substring after {@code delimiter}. For example:
   *
   * <pre>
   *   String file = "/home/path/file.txt";
   *   String ext = Substring.after(last('.')).from(file).orElseThrow(...);
   *   assertThat(ext).isEqualTo("txt");
   * </pre>
   *
   * @since 2.1
   */
  public static Pattern after(Pattern delimiter) {
    return delimiter.map(Match::following);
  }

  /**
   * Returns a {@code Pattern} that will match the substring between {@code open} and {@code close}.
   * For example: <pre>
   *   String quoted = Substring.between(first('('), first(')'))
   *       .from(input)
   *       .orElseThrow(...);
   * </pre>
   *
   * @since 2.1
   */
  public static Pattern between(Pattern open, Pattern close) {
    requireNonNull(open);
    requireNonNull(close);
    return new Pattern() {
      @Override Match match(String input) {
        Match left = open.match(input);
        if (left == null) return null;
        Match right = close.match(input.substring(left.endIndex));
        if (right == null) return null;
        return new Match(input, left.endIndex, left.endIndex + right.startIndex);
      }
    };
  }

  /** A substring pattern that can be matched against a string to find substrings. */
  public static abstract class Pattern {
    /** Matches against {@code string} and returns null if not found. */
    abstract Match match(String input);

    /**
     * Matches this pattern against {@code string}, returning a {@code Match} if successful, or
     * {@code empty()} otherwise.
     *
     * <p>This is useful if you need to call {@link Match} methods, like {@link Match#remove} or
     * {@link Match#before}. If you just need the matched substring itself, prefer to use
     * {@link #from} instead.
     */
    public final Optional<Match> in(String string) {
      return Optional.ofNullable(match(string));
    }

    /**
     * Matches this pattern against {@code string}, returning the matched substring if successful,
     * or {@code empty()} otherwise. {@code pattern.from(str)} is equivalent to
     * {@code pattern.in(str).map(Object::toString)}.
     *
     * <p>This is useful if you only need the matched substring itself. Use {@link #in} if you need
     * to call {@link Match} methods, like {@link Match#remove} or {@link Match#before}.
     *
     * @since 2.1
     */
    public final Optional<String> from(String string) {
      return Optional.ofNullable(Objects.toString(match(string), null));
    }

    /**
     * Matches this pattern against {@code string}, and returns a copy with the matched substring
     * removed if successful. Otherwise, returns {@code string} unchanged.
     */
    public final String removeFrom(String string) {
      Match match = match(string);
      return match == null ? string : match.remove();
    }

    /**
     * Returns a new string with the substring matched by {@code this} replaced by {@code
     * replacement}. Returns {@code string} as-is if a substring is not found.
     */
    public final String replaceFrom(String string, CharSequence replacement) {
      requireNonNull(replacement);
      Match match = match(string);
      return match == null ? string : match.replaceWith(replacement);
    }

    /**
     * Returns a {@code Pattern} that fall backs to using {@code that} if {@code this} fails to
     * match.
     */
    public final Pattern or(Pattern that) {
      requireNonNull(that);
      Pattern base = this;
      return new Pattern() {
        @Override Match match(String input) {
          Match match = base.match(input);
          return match == null ? that.match(input) : match;
        }
      };
    }

    /**
     * Returns a {@code Pattern} that will match from the substring matched by {@code this} to the
     * end of the input string. For example:
     *
     * <pre>
     *   String line = "return foo; // some comment...";
     *   String commentRemoved = first("//").toEnd().removeFrom(line).trim();
     *   assertThat(commentRemoved).isEqualTo("return foo;");
     * </pre>
     *
     * <p>To match from the beginning of the input string to the end of a pattern, use
     * {@link Substring#upToIncluding} instead.
     *
     * @since 2.2
     */
    public final Pattern toEnd() {
      return map(Match::toEnd);
    }

    /**
     * Returns a {@code Collector} that splits each string in a stream into two parts, with the
     * matched substring as the separator. For example:
     *
     * <pre>{@code
     * ImmutableSetMultimap<String, String> keyValues =
     *     readLines(file, UTF_8).stream()
     *         .collect(first(':').splitting(ImmutableSetMultimap::toImmutableSetMultimap));
     * }</pre>
     *
     * <p>If you need to trim the key-value pairs, you can use {@link #splittingTrimmed
     * splittingTrimmed()}. For more flexible use cases, such as transforming the keys or values
     * to another type, one approach is to collect the pairs into a {@code BiStream} first and
     * then chain away:
     *
     * <pre>{@code
     * ImmutableSetMultimap<Key, Value> keyValues =
     *     readLines(file, UTF_8).stream()
     *         .collect(first(':').splitting(BiStream::toBiStream))
     *         .mapKeys(Key::of)
     *         .mapValues(Value::of)
     *         .collect(ImmutableSetMultimap::toImmutableSetMultimap);
     * }</pre>
     * 
     * Or, use the {@link #in} method to operate on the {@link Match} objects, which also allows
     * custom handling of the error of not finding the separator, as well as the ability to report
     * the offending line number:
     *
     * <pre>{@code
     * ImmutableSetMultimap<Key, Value> keyValues =
     *     BiStream.zip(MoreStreams.indexFrom(1), readLines(file, UTF_8).stream())
     *         .mapValues(first(':')::in)
     *         .mapValues((line, m) ->
{'fraction_non_alphanumeric': 0.1411917098445596, 'fraction_numerical': 0.0028231699216155173, 'mean_word_length': 2.696169899337098, 'pattern_counts': {'":': 6, '<': 99, '<?xml version=': 0, '>': 103, 'https://': 4, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 18, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1211080', 'n_tokens_mistral': 8535, 'n_tokens_neox': 8416, 'n_words': 2750}