typed-ident 0.0.2

Type-safe identifier validation, inspection, and mutation.
Documentation
Syntax definitions for how chunks (runs of non-delimiter characters) should be broken up during segmentation.

There is only one provided boundary implementation, but it is configurable.

* [`Standard`], the provided standard boundary implementation
* [`Options`], options for configuring the provided boundary implementation

## Customizing Boundary Rules

There's a few ways to customize how identifying chunk boundaries works.

1. Provide a different [`Options`] type to the [`Standard`] type.
2. Create your own type which implements the [`Boundary`] trait.

For most use-cases, you will not need to implement your own boundary definition.

### Configuring the `Standard` Implementation

The [`Options`] trait defines the following configurations (based on [Unicode Technical Standard #55](https://www.unicode.org/reports/tr55/#Identifier-Chunks)):

* `CAMEL`: Break based on camel-casing rules
  * *An identifier word boundary exists after a lowercase or non-Greek titlecase letter followed by an uppercase or titlecase letter*
* `HAT`: Break based on hat rules ([Unicode Technical Standard #55]https://www.unicode.org/reports/tr55/#Identifier-Chunks)
  * *An identifier word boundary exists before an uppercase or titlecase letter followed by a lowercase letter, or before a non-Greek titlecase letter*
* `DIGIT_TO_LOWER`: Break if we go from an ASCII digit to a lowercase letter.
* `DIGIT_TO_UPPER`: Break if we go from an ASCII digit to an uppercase or titlecase letter.
* `LOWER_TO_DIGIT`: Break if we go from a lowercase or non-greek titlecase letter to an ASCII digit.
* `UPPER_TO_DIGIT`: Break if we go from an uppercase or greek titlecase letter to an ASCII digit.

Given these options, there are three provided options implementations:

* [`Default`]: Only `CAMEL` and `HAT` boundaries.
* [`AllBoundaries`]: All boundary options are enabled.
* [`NoBoundaries`]: No boundary options are enabled.

However, if you want an obscure combination of these options, you can implement your own options type:

```rust
# use typed_ident::*;
# use typed_ident::syntax::*;
# use typed_ident::syntax::boundary::*;
pub struct DigitsOnly(());

// Any constant you *don't* provide will default to `false`.
impl Options for DigitsOnly {
    const DIGIT_TO_LOWER: bool = true;
    const DIGIT_TO_UPPER: bool = true;
    const LOWER_TO_DIGIT: bool = true;
    const UPPER_TO_DIGIT: bool = true;
}

// Use the custom boundary with the generic presets.
use typed_ident::presets::generic::*;
type LowerCamelIdentFromPresets = LowerCamelIdent<
    profile::Ascii, // Select a character profile.
    DigitsOnly,     // Select a boundary implementation.
>;

// Or you can use it with a custom combination of syntax rules.
type CustomizedLowerCamelIdent = Ident<
    boundary::Standard<DigitsOnly>,
    delimiter::HyphenMinus,
    profile::LowerCamel<profile::Ascii>,
>;
```

[`AllBoundaries`]: crate::syntax::boundary::options::AllBoundaries
[`Boundary`]: crate::syntax::boundary::Boundary
[`Default`]: crate::syntax::boundary::options::Default
[`NoBoundaries`]: crate::syntax::boundary::options::NoBoundaries
[`Options`]: crate::syntax::boundary::options::Options
[`Standard`]: crate::syntax::boundary::Standard

### Creating a Custom Boundary Implementation

You can create your own boundary implementation by creating a new type, and then implementing the [`Boundary`] trait on it. It is recommended that this type is
distinct from the other syntax definition types that you have implemented.

You only need to provide three functions:

* [`Boundary::find_boundary`] returns the next boundary from the start of the string
* [`Boundary::rfind_boundary`] returns the next boundary from the end of the string
* [`Boundary::has_boundary_at`] returns whether or not there is a boundary for some provided chunk string, at some provided byte offset

```rust
# use typed_ident::*;
# use typed_ident::syntax::*;
# use typed_ident::syntax::boundary::*;
# use std::num::NonZero;
// Let's define an example boundary implementation, which inserts a boundary
// before every ASCII digit character (not useful, but simple to implement).
//
// This is only a simple example, a more complete implementation should use the
// `Segmentation` type to ensure that it iterates over graphemes properly.
struct CustomBoundary;
impl Boundary for CustomBoundary {
  fn find_boundary<S: Segmentation>(chunk: &str) -> Option<NonZero<usize>> {
    for (idx, c) in chunk.char_indices().skip(1) {
      if c.is_ascii_digit() {
        return NonZero::new(idx);
      }
    }
    None
  }
  fn rfind_boundary<S: Segmentation>(chunk: &str) -> Option<NonZero<usize>> {
    for (idx, c) in chunk.char_indices().rev() {
      if c.is_ascii_digit() {
        return NonZero::new(idx);
      }
    }
    None
  }
  fn has_boundary_at<S: Segmentation>(chunk: &str, idx: usize) -> bool {
    if idx == 0 {
      return false;
    }
    chunk[idx..].chars().next().is_some_and(|c| c.is_ascii_digit())
  }
}

// You must define a custom type alias when using non-standard boundaries.
type CustomizedLowerCamelIdent = Ident<
    CustomBoundary,
    delimiter::HyphenMinus,
    profile::LowerCamel<profile::Ascii>,
>;
```