Expand description
Convenient crate converting conventional casings of Rust identifiers, conveying correct context.
While there are several crates providing casing manipulation functionality, Caseidae is
specifically designed for converting identifiers. It has built-in support for syn::Ident,
handles lifetime annotations, and preserves prefixes like raw identifiers (r#) and
underscores. See the rules for details.
Additionally, general casing conversions don’t convey the same meaning. For instance, instead
of exposing a function named to_snake_case, this crate provides the same conversion under
functions called to_variable_name,
to_field_name,
to_function_name
and so on. A reader of the code doesn’t have to think about what the casing looks like, or why
it was chosen in the particular case (pun accidental) – the intent is clear.
§Example
Imagine we are writing a wild macro that generates functions based on traits, such that
- the function name is the trait name,
- the parameters are the trait’s associated constants,
- the generic type parameters are the trait’s functions,
- the lifetime parameters are the trait’s generic parameters.
For instance, turning
trait HeroHealth<Armor> {
const MAGIC_RESISTANCE: u8 = 20;
fn deal_damage();
// `type` is a keyword, so we need a raw identifier. Caseidae will handle it properly.
fn r#type();
// Caseidae will preserve leading underscores.
fn __hidden_secret();
}into
fn hero_health<'armor, DealDamage, r#Type, __HiddenSecret>(
magic_resistance: u8
) {
// ...
}Then our code might look like this:
use caseidae::{ToFunctionName, ToLifetimeName, ToTypeName, ToVariableName};
use proc_macro2::{Span, TokenStream};
use quote::quote;
use syn::Ident;
fn trait_to_function(
name: Ident,
// The tuple is for name and type.
associated_constants: &[(Ident, Ident)],
generic_type_parameters: &[Ident],
functions: &[Ident],
) -> TokenStream {
let function_name = name.to_function_name();
let function_lifetime_parameters = generic_type_parameters
.iter()
.map(|parameter| parameter.to_lifetime_name());
let function_generic_type_parameters = functions
.iter()
.map(|function| function.to_type_name());
let function_parameters = associated_constants
.iter()
.map(|(constant_name, constant_type)| {
let parameter_name = constant_name.to_variable_name();
quote! (#parameter_name: #constant_type)
});
quote! {
fn #function_name<
#(#function_lifetime_parameters,)*
#(#function_generic_type_parameters,)*
>(#(#function_parameters,)*) {
// ...
}
}
}You can test this in
examples/macro.rs
by running
cargo run --example macro§General rules for conversions
All performed conversions follow a set of common rules.
-
Some prefixes and suffixes are preserved:
- Leading
raw identifier
prefixes (
r#), only the first level (e.g.to_struct_nameturnsr#r#r#oarintor#RROar). - Leading or trailing underscores (
_), any number of them. In particular, this means that underscore expressions or wildcard patterns don’t get destroyed.
In order for both of these to be preserved, they must appear in the correct order (
r#and then some number of_). Note that if a single tick (') is present at the start of the input, it is considered a lifetime prefix and is trimmed before detectingr#and_. Thus, for example,'r#_fnis turned intor#_Fnbyto_trait_name. However, whitespace or any other characters are not trimmed. - Leading
raw identifier
prefixes (
-
The input is split into words (later concatenated according to the output casing) by a set of boundaries:
- If the input matches screaming snake case, then the only boundary is underscore.
- Otherwise, the boundaries are
- any character that is not ASCII alphanumeric,
- an uppercase letter,
- a digit followed by a letter,
- a letter followed by a digit.
In particular, this means that:
- Outside of screaming case conversions, sequences of uppercase letters are considered
sequences of one-letter words. For example,
TCPServeris turned intot_c_p_serverbyto_field_name. - Numbers (sequences of digits) are considered separate words. For example,
vector180rotationis turned intoVector180Rotationbyto_enum_nameand intovector_180_rotationbyto_function_name.
-
The output is not checked to be a valid Rust identifier. The implementations on
syn::Identare checked implicitly by the type, but still can collide with keywords. The implementations onstrperform no checks whatsoever. For some use cases this is not a problem, for instance it’s okay if a macro generates invalid code, you get a compilation error and fix the inputs. If this is not acceptable, you might utilize a crate like check_keyword, convert_string, or similar.
Traits§
- ToAttribute
Name - Types convertible to the letter casing corresponding to names of attributes.
- ToConstant
Name - Types convertible to the letter casing corresponding to names of constant items.
- ToCrate
Name - Types convertible to the letter casing corresponding to names of crates.
- ToField
Name - Types convertible to the letter casing corresponding to names of fields.
- ToFunction
Name - Types convertible to the letter casing corresponding to names of functions.
- ToLifetime
Name - Types convertible to the letter casing corresponding to names of lifetimes.
- ToMacro
Name - Types convertible to the letter casing corresponding to names of macros.
- ToModule
Name - Types convertible to the letter casing corresponding to names of modules.
- ToPrimitive
Name - Types convertible to the letter casing corresponding to names of primitives.
- ToStatic
Name - Types convertible to the letter casing corresponding to names of static items.
- ToTrait
Name - Types convertible to the letter casing corresponding to names of traits.
- ToType
Name - Types convertible to the letter casing corresponding to names of types.
- ToVariable
Name - Types convertible to the letter casing corresponding to names of variables.
- ToVariant
Name - Types convertible to the letter casing corresponding to names of enum variants.