# regex-range

Compile an inclusive integer range `[min, max]` into the smallest practical
regex source string. The output matches the decimal form of every integer in
the range and nothing outside it.

The function returns a source string, not a compiled pattern. Wrap it in `^...$`
and hand it to your regex engine.

## Install

```toml
[dependencies]
regex-range = "0.1"
```

## Usage

```rust
use regex_range::{to_regex_range, Options};

let opts = Options::default();

assert_eq!(to_regex_range("1", Some("5"), &opts).unwrap(), "[1-5]");
assert_eq!(
    to_regex_range("10", Some("50"), &opts).unwrap(),
    "(?:1[0-9]|[2-4][0-9]|50)"
);
assert_eq!(
    to_regex_range("-10", Some("10"), &opts).unwrap(),
    "(?:-[1-9]|-?10|[0-9])"
);
```

A single argument, or `max` equal to `min`, returns the value verbatim:

```rust
# use regex_range::{to_regex_range, Options};
assert_eq!(to_regex_range("5", None, &Options::default()).unwrap(), "5");
```

When `min` is greater than `max`, the bounds swap:

```rust
# use regex_range::{to_regex_range, Options};
assert_eq!(
    to_regex_range("55", Some("10"), &Options::default()).unwrap(),
    "(?:1[0-9]|[2-4][0-9]|5[0-5])"
);
```

## Inputs

Inputs are decimal integer strings. A leading `-` and leading zeros are allowed.
Surrounding whitespace is trimmed. Blank strings, non-numeric strings, values
outside `i64`, and `i64::MIN` are rejected with an [`Error`]. Values up to
fifteen digits are covered by the test suite.

## Options

| Option | Default | Effect |
| --- | --- | --- |
| `relax_zeros` | on | Make leading zeros optional in padded output (`0?`, `0{0,2}`). Set to `false` for mandatory zeros (`0`, `00`). |
| `strict_zeros` | unset | Inverse alias of `relax_zeros`. `true` turns relaxing off. |
| `shorthand` | off | Emit `\d` instead of `[0-9]` for full-digit positions. |
| `capture` | off | Wrap the whole result in a capturing group `(...)`. Wraps even a single alternative. Takes precedence over `wrap`. |
| `wrap` | on | Wrap a multi-alternative result in a non-capturing group `(?:...)`. Set to `false` to suppress it. |

```rust
use regex_range::{to_regex_range, Options};

// Optional leading zeros (default).
let src = to_regex_range("001", Some("100"), &Options::default()).unwrap();
assert_eq!(src, "(?:0{0,2}[1-9]|0?[1-9][0-9]|100)");

// Mandatory leading zeros.
let strict = Options::default().relax_zeros(false);
assert_eq!(to_regex_range("001", Some("005"), &strict).unwrap(), "00[1-5]");

// Shorthand digit class.
let short = Options::default().shorthand(true);
assert_eq!(
    to_regex_range("0", Some("999999"), &short).unwrap(),
    "(?:\\d|[1-9]\\d{1,5})"
);
```

## Padding

A zero-padded bound makes the range padded. The generated pattern then allows
leading zeros up to the width of `max`. Padding is detected as an optional sign,
a leading `0`, then at least one more digit. The trailing digit may itself be
`0`, so `"00"` is padded. A lone `"0"` is not padded.

```rust
# use regex_range::{to_regex_range, Options};
let opts = Options::default();
assert_eq!(to_regex_range("01", Some("05"), &opts).unwrap(), "0?[1-5]");
assert_eq!(to_regex_range("0", Some("00"), &opts).unwrap(), "0?0");
assert_eq!(to_regex_range("0", Some("9"), &opts).unwrap(), "[0-9]");
```

## Output syntax

The output uses a small subset of regex syntax shared across engines:

- Character classes `[a-b]` and `[ab]`, and the digit class `[0-9]` or `\d`.
- Alternation `|`.
- Quantifiers `{n}`, `{n,m}`, and `?`.
- Groups `(?:...)` and `(...)`.
- A literal `-` or optional `-?` sign.

No backreferences or lookaround appear, so the source compiles directly with the
[`regex`](https://crates.io/crates/regex) crate.

## Caching

Results are memoized in a process-wide cache. [`clear_cache`] empties it. The
cache is a speed optimization and does not change any returned string.

## License

Licensed under the [MIT license](LICENSE).
