tokel_std/string.rs
1//! String and text-manipulation Tokel [`Transformer`]s.
2
3use proc_macro2::{Ident, Span, TokenStream};
4
5use syn::parse::Nothing;
6
7use tokel_engine::prelude::{Registry, Transformer};
8
9/// A transformer that concatenates all input tokens into a single identifier.
10///
11/// It ignores standard spacing and simply glues the string representations
12/// of the tokens together.
13///
14/// # Example
15/// `[< hello _ world >]:concatenate` -> `hello_world`
16#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
17pub struct Concatenate;
18
19impl Transformer for Concatenate {
20 fn transform(
21 &mut self,
22 input: TokenStream,
23 argument: TokenStream,
24 ) -> Result<TokenStream, syn::Error> {
25 // Concatenate takes no arguments, so we enforce that the `[[...]]` is empty.
26 let _: Nothing = syn::parse2(argument)?;
27
28 // If the input is completely empty, just return empty.
29 if input.is_empty() {
30 return Ok(input);
31 }
32
33 let mut concatenated_string = String::new();
34 let mut first_span = Span::call_site();
35 let mut is_first = true;
36
37 for tree in input {
38 if is_first {
39 first_span = tree.span();
40
41 is_first = false;
42 }
43
44 // `to_string()` on a TokenTree strips `r#` from idents and handles raw strings nicely.
45 concatenated_string.push_str(&tree.to_string());
46 }
47
48 // We must ensure the resulting string is a valid Rust identifier.
49 // `syn::Ident::new` will panic if the string is not a valid ident (e.g. if it starts with a number).
50 // To be safe, we try to parse it. If it fails, we return a syn::Error.
51 let parsed_ident = syn::parse_str::<Ident>(&concatenated_string).map_err(|_| {
52 syn::Error::new(
53 first_span,
54 format!("concatenated string `{concatenated_string}` is not a valid identifier"),
55 )
56 })?;
57
58 Ok(quote::quote_spanned!(first_span=> #parsed_ident))
59 }
60}
61
62/// Inserts all `string`-related [`Transformer`]s into the specified [`Registry`].
63///
64/// # Errors
65///
66/// This will fail if at least one standard `string`-related [`Transformer`] is already present by-name in the [`Registry`].
67///
68/// On failure, there is no guarantee that other non-colliding transformers have not been registered.
69#[inline]
70pub fn register(registry: &mut Registry) -> Result<(), Box<dyn Transformer>> {
71 registry
72 .try_insert("concatenate", Concatenate)
73 .map_err(Box::new)
74 .map_err(|target_value| target_value as Box<dyn Transformer>)?;
75
76 Ok(())
77}