tweld 1.0.0

Dynamic identifier generation for Rust macros. Tweld provides a flexible @[] syntax to "fuse" strings, case-conversions, and logic directly into your generated source code.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
# Tweld

[![Crates.io](https://img.shields.io/crates/v/tweld.svg)](https://crates.io/crates/tweld)
[![Docs.rs](https://docs.rs/tweld/badge.svg)](https://docs.rs/tweld)
[![License](https://img.shields.io/crates/l/tweld.svg)](https://choosealicense.com/licenses/)
[![CI](https://github.com/marcelo-ferraz/tweld/actions/workflows/ci.yml/badge.svg)](https://github.com/marcelo-ferraz/tweld/actions/workflows/ci.yml)

> *You can read it as tiny-weld, token-weld, or just tweld. The important thing is that it compiles.*
 
Tweld is a procedural macro toolkit and naming DSL for Rust. It lets you dynamically generate, modify, and compose identifiers directly in your source code using a clean `@[]` syntax — because sometimes the identifier you need doesn't quite exist yet, and writing a full proc-macro just to rename a function feels like bringing a freight train to post a letter.
One can only hope the syntax is clean and intuitive enough.

```rust
weld!("## @[(the idea | title)]");
```
 
The name comes from the idea of fusing tokens together. It started as a tool for writing macros for macros (which sounds recursive, because it is), and then grew somewhat beyond its original remit.

---
 
## Installation
 
Add it to your `Cargo.toml`:
 
```toml
[dependencies]
tweld = "0.1.0-rc.2"  # check crates.io for the latest
```
 
Or, if you prefer:
 
```sh
cargo add tweld
```
 
Then bring it into scope:
 
```rust
use tweld::weld;
```
 
---

## The Core Idea
 
Everything in tweld revolves around one macro — `weld!` — and one syntax: `@[...]`, I fondly call it the interpolator, but the final name is pending.
 
Anything inside `@[...]` gets transformed, *welded* and fused (not literally — the implications would be unwieldy), and emitted as either an identifier or the content of a string literal. A chain of modifiers separated by `|` transforms the value step by step.
 
```rust
weld!(
    fn @[(get user profiles) | snek]() { ... }
    // renders: fn get_user_profiles() { ... }
);
```
 
The modifiers don't need to produce something sensible at every intermediate step. They just need to produce something valid by the end. What happens in between is your own affair.
 
---

 
## Groups
 
Inside `@[...]`, you can organise tokens into named groups and apply modifiers to them. There are two kinds.
 
### Single-value groups `(...)`
 
Tokens inside `()` are concatenated into a single value before any modifiers are applied. Think of it as: *everything in here is one thing*.
 
```rust
weld!(
    const @[(_super Duper) | snek | substr{1,}] = "";
    // renders: const super_duper = "";
);
```
 
### List groups `[...]`
 
Tokens inside `[]` are kept as a *collection*. Modifiers that work on individual values (casing, replace, trim, etc.) are applied to each item independently. Modifiers that work on structure (reverse, join, slice, etc.) operate on the collection as a whole.
 
```rust
weld!(
    const @[([er sup] | reverse) _duper] = "";
    // renders: const super_duper = "";
    // ('reverse' flips the order of items in the list, not the characters within them)
);
```
 
Groups can be nested, and modifiers chain naturally across levels:
 
```rust
weld!(
    struct @[([([er sup] | reverse) -duper] | camel) | pascal] {
        pub id: i64,
    };
    // renders: struct SuperDuper { ... }
);
```
 
This is a contrived example — but the point is that you can compose arbitrarily, and as long as the final result is a valid Rust identifier, the compiler will be perfectly happy and won't ask any questions.
 


## Raw Indentifiers

Raw identifiers are handled automatically. If a token is a reserved word, you can pass it in using Rust's `r#` prefix — `r#loop`, `r#type`, and so on — and `weld!` will accept it without complaint. In the other direction, if the result of a modifier chain happens to land on a reserved word, the `r#` prefix will be added for you. If you passed a raw identifier in but the transformations produced something that no longer needs it, the prefix is quietly removed. You shouldn't have to think about it either way.

 
 
## Modifiers
 
Modifiers are chained with `|` inside a group. Each one receives the output of the previous step.
 
### Casing
 
Casing modifiers use the [`heck`](https://docs.rs/heck) crate under the hood.
 
| Modifier          | Aliases                     | Example output |
|-------------------|-----------------------------|----------------|
| `lowercase`       | `lower`                     | `hello_world`  |
| `uppercase`       | `upper`                     | `HELLO_WORLD`  |
| `pascalcase`      | `pascal`, `uppercamelcase`  | `HelloWorld`   |
| `camelcase`       | `camel`, `lowercamelcase`   | `helloWorld`   |
| `snakecase`       | `snek`, `snake`, `snekcase` | `hello_world`  |
| `titlecase`       | `title`                     | `Hello World`  |
| `kebabcase`       | `kebab`                     | `hello-world`  |
| `traincase`       | `train`                     | `Hello-World`  |
| `shoutykebabcase` | `shoutykebab`               | `HELLO-WORLD`  |
| `shoutysnakecase` | `shoutysnake`, `shoutysnek` | `HELLO_WORLD`  |
 
> **A note on tokens vs strings:** When applied to identifiers (function names, struct names, etc.), `kebabcase`, `traincase`, and `shoutykebabcase` won't work — hyphenated identifiers aren't valid Rust. `titlecase` will behave like `pascalcase` in that context. When applied to string literals, all of them work as intended.
 
### `singular` / `plural`
 
`singular` strips a trailing `s`; `plural` adds one. No linguistic analysis is happening here — it's string manipulation wearing a vocabulary waistcoat.
 
```rust
weld!(
    pub struct @[Users | singular | pascal] { pub id: i64 }
    // renders: pub struct User { ... }
);
```
 
---
 
### `replace{pattern, replacement}`
 
Replaces all non-overlapping occurrences of a pattern with a replacement string.
 
```rust
weld!(const @[(a long ident) | replace{"long", "small"} | snek] = "";);
// renders: const a_small_ident = "";
```
 
---
 
### `substr{start?, end?}` / `substring`
 
Returns the substring from `start` up to (not including) `end`. Both are optional. Indexes are zero-based.
 
```rust
weld!(const @[(a long identifier) | substr{, 9} | snek] = "";);
// renders: const a_long_ident = "";
```
 
---
 
### `reverse` / `rev`
 
On a single value: reverses the characters.
On a list group: reverses the order of items (not the characters within them).
 
```rust
weld!(const @[(no lemon no melon) | reverse | snek] = "";);
// renders: const nolem_on_nomel_on = "";
```
 
---
 
### `repeat{n}` / `rep` / `times`
 
Creates a new value by repeating the current value `n` times.
 
```rust
weld!(const rawhide = @[",rolling' " | times{3} | substr{1}]);
// renders: const rawhide = "rolling' ,rolling' ,rolling' ";
```
 
---
 
### `split{separator}`
 
Splits the value by a character, string, or index (any integer > 0).
 
The behaviour differs between group types:
- In a **single-value group** `(...)`: splits the concatenated value into pieces.
- In a **list group** `[...]`: splits each item individually, adding the results back into the collection at that position.
 
**Splitting by character or string:**
 
```rust
// Single-value group: splits on '-', lowercases each part, joins with ", "
weld!(const val = @[(("get-one" two - "3-4" Struct) | split{'-'} | lower | join{", "})]);
// renders: const val = "get, onetwo, 3, 4struct";
 
// List group: each item is split individually
weld!(const val = @[["get-one" two - "3-4" Struct] | split{"-"} | lower | join{", "}]);
// renders: const val = "get, one, two, 3, 4, struct";
```
 
**Splitting by index:**
 
Splits the value every N characters. If the index is larger than the value's length, the argument is ignored.
 
```rust
weld!(const val = @[(("get-" Test - Struct) | split{6} | lower | join{"_"})]);
// renders: const val = "get-te_st-struct";
 
weld!(const val = @[["get-" Test - Struct] | split{2} | lower | join{","}]);
// renders: const val = "ge,t-,te,st,-,st,ruct";
```
 
---
 
### `join{separator?}`
 
Flattens a list into a single value, with an optional separator between items. If the current value is already a single value, it passes through unchanged.
 
```rust
weld!(const val = @[["get-" Test - Struct] | join{","}]);
// renders: const val = "get-,Test,-,Struct";
```
 
---
 
### `padstart{length, pad}` / `padleft` / `padl`
 
Pads from the **start** of the value until it reaches `length` characters. The pad string is repeated and/or truncated as needed. If the value is already at or beyond `length`, it's returned unchanged.
 
```rust
weld!(const val = @[("get-" Test-Struct) | padleft{20, "-"}]);
// renders: const val = "-----get-Test-Struct"
//          (total length 20, padded with '-' on the left)
```
 
---
 
### `padend{length, pad}` / `padright` / `padr`
 
Same as `padstart`, but pads from the **end**.
 
```rust
weld!(const val = @[("get-" Test-Struct) | padright{20, "-"}]);
// renders: const val = "get-Test-Struct-----"
```
 
---
 
### `slice{start?, end?}`
 
Extracts a portion of the string. Both positions are optional and support **negative indexing** (counting backwards from the end). If `start` is greater than `end`, returns an empty value.
 
```rust
weld!(const val = @["get_" Test_Struct | slice{5}]);
// renders: const val = "get_Struct"
 
weld!(const val = @[("_get_" Test_Struct) | slice{1, -4}]);
// renders: const val = "get_Test_St"
 
weld!(const val = @[("_get_" Test_Struct) | slice{-6, -4}]);
// renders: const val = "St"
 
weld!(const val = @["get_" Test_Struct | slice{-4, -6}]);
// renders: const val = ""  (start > end)
```
 
---
 
### `splice{mode?, start?, end?, replacement?}`
 
The most involved modifier. Removes a range from the value, optionally replaces it with new content, and returns either the modified value or the removed portion — depending on the mode.
 
**Modes:**
 
| Mode keywords          | Returns                                               |
|------------------------|-------------------------------------------------------|
| `into`, `val`, `value` | The modified string (with the range removed/replaced) |
| `out`, `removed`, `rm` | The removed portion                                   |
 
**Basic removal:**
 
```rust
// Remove from position 1 onwards → return the result
weld!(const val = @[("get_" Test_Struct) | splice{into, 1}]);
// renders: const val = "g"
 
// Remove from position 1 onwards → return what was removed
weld!(const val = @[("get_" Test_Struct) | splice{out, 1}]);
// renders: const val = "et_Test_Struct"
```
 
**Removing a range:**
 
```rust
weld!(const val = @[("get_" Test_Struct) | splice{into, 1, 4}]);
// renders: const val = "gTest_Struct"
 
weld!(const val = @[("get_" Test_Struct) | splice{out, 1, 4}]);
// renders: const val = "et_"
```
 
**Replacing a range:**
 
```rust
weld!(const val = @[("get_" Test_Struct) | splice{value, 1, 4, "ot_"}]);
// renders: const val = "got_Test_Struct"
 
// Omit start to replace from the beginning
weld!(const val = @[("get_" Test_Struct) | splice{val, , 4, "got_"}]);
// renders: const val = "got_Test_Struct"
 
// Omit end to replace to the end of the string
weld!(const val = @[("get_" Test_Struct) | splice{value, 1, , "ot_"}]);
// renders: const val = "got_"
 
// Omit both to replace the entire value
weld!(const val = @[("get_" Test_Struct) | splice{val, , , "new"}]);
// renders: const val = "new"
```
 
**Negative positions** count from the end:
 
```rust
weld!(const val = @[("get_" Test_Struct) | splice{into, -4}]);
// renders: const val = "get_Test_St"
 
weld!(const val = @[("get_" Test_Struct) | splice{into, -4, -1, "<->"}]);
// renders: const val = "get_Test_St<->t"
```
 
**Aliases:** `splice_into` and `splice_out` are shorthand for `splice{into, ...}` and `splice{out, ...}`:
 
```rust
weld!(const val = @[("get_" Test_Struct) | splice_out{-4, -1}]);
// renders: const val = "ruc"
 
weld!(const val = @[("get_" Test_Struct) | splice_into{-4, -1, "<->"}]);
// renders: const val = "get_Test_St<->t"
```
 
---
 
## A More Complete Example
 
Here's what the modifier chain looks like when used for something you might actually want to do:
 
```rust
use tweld::weld;
 
weld! {
    pub struct @[Users | singular | pascal] {
        pub id: i64,
    }
 
    impl @[Users | singular | pascal] {
 
        // Generates: pub fn get_user_profile_by_id(id: i64)
        pub fn @[((get_ UserProfiles) | replace{"s", ""} | snek) _by_id](id: i64) {
            println!("Fetching @[Users | singular | lower] {}...", id);
        }
    }
}
```
 
The modifiers don't need to be tidy on the inside. They just need to produce something valid on the outside — which is, when you think about it, a reasonable standard to hold most things to.
 
## Some real world examples

### Defining "update by id" functions
In this example I want to try to fix table names like UsersProfiles, or TagNames to use them to form identifiers in a way that rust needs and literals that are more human readable.

```rust
#[macro_export]
macro_rules! define_update_by_id {
    ($user_table:ident as $model:ident values $changesetTp:ident) => {
        tweld::weld! {
            #[doc = @["Updates the " ($user_table | split{'_'} | singular | join{' '} ) ", using the id as the filter."]]
            pub fn @[update_ ($user_table | split{'_'} | singular | join{'_'} ) _by_id](id: i64, change_set: $changesetTp) -> anyhow::Result<$model> {
                log::info!(@["Saving " ($user_table | split{'_'} | singular | join{' '} ) " {:?}..."], change_set);                
                crate::update!($user_table as $model values change_set by id)
            }
        }
    };

    ($($user_table:ident as $model:ident values $changesetTp:ident),* $(,)?) => {
        $(define_update_by_id!($user_table:ident as $model:ident values $changesetTp:ident))*
    }
}
```

And this macro can be used this way:

```rust
define_update_by_id!(
    users as User values UserChangeSet,
    users_profiles as UserProfile values UserProfileChangeSet
);
```
> This flow: `$user_table | split{'_'} | singular | join{'_'} )`, will process the partial identifier this way:     
`UsersProfiles` -> `[users profiles]` -> `[user profile]` -> `user_profile`

```rust
#[doc = "Updates the user profile, using the id as the filter."]
pub fn update_user_profile_by_id(id:i64,change_set:UserChangeSet) -> anyhow::Result<User>{
    log::info!("Saving user profile, {:?}",change_set);
    crate::update!(users as User values change_set by id)
}

#[doc = "Updates the user, using the id as the filter."]
pub fn update_user_by_id(id:i64,change_set:UserChangeSet) -> anyhow::Result<User>{
    log::info!("Saving user, {:?}",change_set);
    crate::update!(users as User values change_set by id)
}

```

### Defining connection structs
In one of my projects I need to have [connection](https://www.apollographql.com/blog/explaining-graphql-connections) structs for pagination. Since this case was a bit straight forward, I created a macro to define the connection type based on a model type.

```rust
    ($($ty:ty),* $(,)?) => {
        $(
            tweld::weld! {
                #[derive(SimpleObject)]
                #[graphql(complex)]
                pub struct @[($ty Connection)|pascal] {
                    pub items: Vec<$ty>,
                    pub page_index: i32,
                    pub page_size: i32,
                    pub total_count: i64,
                }
            }

            tweld::weld! {
                #[ComplexObject]
                impl @[($ty Connection)|pascal] {
                    async fn total_pages(&self) -> i64 {
                        let result: f64 = (self.total_count as f64 / (self.page_size as f64)) as f64;
                        result.ceil() as i64
                    }

                    async fn has_next(&self) -> bool {
                        self.total_count - ((self.page_index * self.page_size) as i64) > 0
                    }
    
                    async fn has_previous(&self) -> bool {
                        self.page_index > 0
                    }
                } 
            }
        )*
    };
```
When used like this:
```rust
define_connections!(User);
```

Will generate:
```rust
#[derive(SimpleObject)]
#[graphql(complex)]
pub struct UserConnection {
    pub items:Vec<User>,
    pub page_index:i32,
    pub page_size:i32,
    pub total_count:i64,
}

#[ComplexObject]
impl UserConnection {
    async fn total_pages(&self) -> i64 {
        let result:f64 = (self.total_count as f64/(self.page_size as f64))as f64;
        result.ceil() as i64
    }
    
    async fn has_next(&self) -> bool {
        self.total_count - ((self.page_index*self.page_size) as i64)>0
    }

    async fn has_previous(&self) -> bool {
        self.page_index > 0
    }
}
```
 
## Status
 
Tweld is currently in version `1.0`. Testing is still an ongoing endeavor, some will say is a mess, I call it home. I will aim for the api to be stable, respecting semantic versioning.
 
Bug reports, feature requests, and strong opinions about identifier naming are all welcome.
 
---
 
## License
 
Licensed under either of [MIT](LICENSE-MIT) or [Apache-2.0](LICENSE-APACHE) at your option.