irongit

A rust macro that allows regular expression creation through independent files. Allows fully typed named groups.

example works

huncholanehuncholaneauthored
commit 5fb0aa5bfab8954a54d6f700ecb640bf66dd891cBrowse files

9 files changed, +385 -0

+4-0.cargo/config.toml
@@ -0,0 +1,4 @@
1+[alias]
2+tr = "test --release"
3+e = "run --example event"
4+b = "build"
+1-0.gitignore
@@ -0,0 +1 @@
1+target
+145-0Cargo.lock
@@ -0,0 +1,145 @@
1+# This file is automatically @generated by Cargo.
2+# It is not intended for manual editing.
3+version = 4
4+
5+[[package]]
6+name = "aho-corasick"
7+version = "1.1.3"
8+source = "registry+https://github.com/rust-lang/crates.io-index"
9+checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
10+dependencies = [
11+ "memchr",
12+]
13+
14+[[package]]
15+name = "itoa"
16+version = "1.0.14"
17+source = "registry+https://github.com/rust-lang/crates.io-index"
18+checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674"
19+
20+[[package]]
21+name = "lazy_static"
22+version = "1.5.0"
23+source = "registry+https://github.com/rust-lang/crates.io-index"
24+checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
25+
26+[[package]]
27+name = "memchr"
28+version = "2.7.4"
29+source = "registry+https://github.com/rust-lang/crates.io-index"
30+checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
31+
32+[[package]]
33+name = "proc-macro2"
34+version = "1.0.93"
35+source = "registry+https://github.com/rust-lang/crates.io-index"
36+checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99"
37+dependencies = [
38+ "unicode-ident",
39+]
40+
41+[[package]]
42+name = "quote"
43+version = "1.0.38"
44+source = "registry+https://github.com/rust-lang/crates.io-index"
45+checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
46+dependencies = [
47+ "proc-macro2",
48+]
49+
50+[[package]]
51+name = "regex"
52+version = "1.11.1"
53+source = "registry+https://github.com/rust-lang/crates.io-index"
54+checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
55+dependencies = [
56+ "aho-corasick",
57+ "memchr",
58+ "regex-automata",
59+ "regex-syntax",
60+]
61+
62+[[package]]
63+name = "regex-automata"
64+version = "0.4.9"
65+source = "registry+https://github.com/rust-lang/crates.io-index"
66+checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
67+dependencies = [
68+ "aho-corasick",
69+ "memchr",
70+ "regex-syntax",
71+]
72+
73+[[package]]
74+name = "regex-syntax"
75+version = "0.8.5"
76+source = "registry+https://github.com/rust-lang/crates.io-index"
77+checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
78+
79+[[package]]
80+name = "regex_macro"
81+version = "0.1.0"
82+dependencies = [
83+ "lazy_static",
84+ "proc-macro2",
85+ "quote",
86+ "regex",
87+ "serde",
88+ "serde_json",
89+ "syn",
90+]
91+
92+[[package]]
93+name = "ryu"
94+version = "1.0.19"
95+source = "registry+https://github.com/rust-lang/crates.io-index"
96+checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd"
97+
98+[[package]]
99+name = "serde"
100+version = "1.0.217"
101+source = "registry+https://github.com/rust-lang/crates.io-index"
102+checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70"
103+dependencies = [
104+ "serde_derive",
105+]
106+
107+[[package]]
108+name = "serde_derive"
109+version = "1.0.217"
110+source = "registry+https://github.com/rust-lang/crates.io-index"
111+checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
112+dependencies = [
113+ "proc-macro2",
114+ "quote",
115+ "syn",
116+]
117+
118+[[package]]
119+name = "serde_json"
120+version = "1.0.138"
121+source = "registry+https://github.com/rust-lang/crates.io-index"
122+checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949"
123+dependencies = [
124+ "itoa",
125+ "memchr",
126+ "ryu",
127+ "serde",
128+]
129+
130+[[package]]
131+name = "syn"
132+version = "2.0.96"
133+source = "registry+https://github.com/rust-lang/crates.io-index"
134+checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80"
135+dependencies = [
136+ "proc-macro2",
137+ "quote",
138+ "unicode-ident",
139+]
140+
141+[[package]]
142+name = "unicode-ident"
143+version = "1.0.14"
144+source = "registry+https://github.com/rust-lang/crates.io-index"
145+checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
+16-0Cargo.toml
@@ -0,0 +1,16 @@
1+[package]
2+name = "regex_macro"
3+version = "0.1.0"
4+edition = "2021"
5+
6+[dependencies]
7+syn = "2.0"
8+quote = "1.0"
9+proc-macro2 = "1.0"
10+regex = "1.0"
11+lazy_static = "1.5.0"
12+serde = { version = "1.0.217", features = ["derive"] }
13+serde_json = "1.0.138"
14+
15+[lib]
16+proc-macro = true
+9-0examples/event.rs
@@ -0,0 +1,9 @@
1+use regex_macro::load_regex_files;
2+
3+fn main() {
4+ load_regex_files!("tests/regex");
5+ let events = Event::vec_from_file("tests/samples/events.txt").unwrap();
6+ for event in events {
7+ println!("{}", event)
8+ }
9+}
+185-0src/lib.rs
@@ -0,0 +1,185 @@
1+extern crate proc_macro;
2+use proc_macro::TokenStream;
3+use quote::quote;
4+use std::fs;
5+use syn::{parse_macro_input, Ident, LitStr};
6+
7+#[proc_macro]
8+pub fn load_regex_files(input: TokenStream) -> TokenStream {
9+ let folder_path = parse_macro_input!(input as LitStr).value();
10+
11+ let folder_path =
12+ std::path::PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()).join(folder_path);
13+
14+ // Read files from the directory
15+ let files = fs::read_dir(folder_path)
16+ .expect("Failed to read directory")
17+ .filter_map(|entry| {
18+ let entry = entry.ok()?;
19+ let path = entry.path();
20+
21+ if path.extension().and_then(|ext| ext.to_str()) == Some("re") {
22+ Some(path)
23+ } else {
24+ None
25+ }
26+ })
27+ .collect::<Vec<_>>();
28+
29+ // Generate enum variants and compiled regex objects
30+ let variants = files.iter().map(|path| {
31+ let variant_name = path.file_stem().unwrap().to_str().unwrap();
32+ let ident = Ident::new(variant_name, proc_macro2::Span::call_site());
33+ quote! {
34+ pub #ident: regex::Regex
35+ }
36+ });
37+
38+ // Generate struct initialization
39+ let init_fields = files.iter().map(|path| {
40+ let variant_name = path.file_stem().unwrap().to_str().unwrap();
41+ let ident = Ident::new(variant_name, proc_macro2::Span::call_site());
42+ let file_path = path.to_str().unwrap();
43+ quote! {
44+ #ident: regex::Regex::new(&format!(r"(?mx){}", include_str!(#file_path))).unwrap()
45+ }
46+ });
47+
48+ // Generate the enum and regex accessor
49+ let expanded = quote! {
50+ pub struct RegexFile {
51+ #(#variants),*
52+ }
53+
54+ impl RegexFile {
55+ pub fn new() -> Self {
56+ RegexFile {
57+ #(#init_fields),*
58+ }
59+ }
60+ }
61+
62+ lazy_static::lazy_static! {
63+ pub static ref RE: RegexFile = RegexFile::new();
64+ }
65+
66+
67+ };
68+
69+ let structs = files.iter().map(|path| {
70+ let variant_name = path.file_stem().unwrap().to_str().unwrap();
71+ let ident = Ident::new(variant_name, proc_macro2::Span::call_site());
72+ let file_text = fs::read_to_string(path).unwrap();
73+ let re_str = format!(r"(?mx){}", file_text);
74+ let re = regex::Regex::new(&re_str).unwrap();
75+
76+ // Collect fields into a Vec so they can be used multiple times
77+ let fields: Vec<_> = re
78+ .capture_names()
79+ .flatten()
80+ .map(|name| {
81+ let ident = Ident::new(name, proc_macro2::Span::call_site());
82+ quote! {
83+ pub #ident: Option<SimpleMatch>
84+ }
85+ })
86+ .collect();
87+
88+ let field_initializers: Vec<_> = re
89+ .capture_names()
90+ .flatten()
91+ .map(|name| {
92+ let ident = Ident::new(name, proc_macro2::Span::call_site());
93+ quote! {
94+ #ident: SimpleMatch::from_match(captures.name(#name))
95+ }
96+ })
97+ .collect();
98+
99+ quote! {
100+ #[derive(Debug, Clone, serde::Serialize)]
101+ pub struct #ident {
102+ pub start_pos: usize,
103+ pub end_pos: usize,
104+ #(#fields),*
105+ }
106+
107+ impl std::fmt::Display for #ident {
108+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
109+ write!(f, "{}", serde_json::to_string_pretty(&self).unwrap())
110+ }
111+ }
112+
113+ impl #ident {
114+ pub fn from_str(text: &str) -> Option<Self> {
115+ let re = &RE.#ident;
116+ let captures = re.captures(text);
117+ if let Some(captures) = captures {
118+ Some(Self {
119+ start_pos: captures.get(0).unwrap().start(),
120+ end_pos: captures.get(0).unwrap().end(),
121+ #(#field_initializers),*
122+ })
123+ } else {
124+ None
125+ }
126+ }
127+
128+ pub fn from_file(filename: &str) -> Result<Option<Self>, std::io::Error> {
129+ let text = std::fs::read_to_string(filename)?;
130+ Ok(Self::from_str(&text))
131+ }
132+
133+ pub fn vec_from_str(text: &str) -> Vec<Self> {
134+ let re = &RE.#ident;
135+ re.captures_iter(text)
136+ .map(|captures| {
137+ Self {
138+ start_pos: captures.get(0).unwrap().start(),
139+ end_pos: captures.get(0).unwrap().end(),
140+ #(#field_initializers),*
141+ }
142+ })
143+ .collect()
144+ }
145+
146+ pub fn vec_from_file(filename: &str) -> Result<Vec<Self>, std::io::Error> {
147+ let text = std::fs::read_to_string(filename)?;
148+ Ok(Self::vec_from_str(&text))
149+ }
150+ }
151+ }
152+ });
153+
154+ let expanded = quote! {
155+ #expanded
156+ #(#structs)*
157+
158+ #[derive(Debug, Clone, serde::Serialize)]
159+ pub struct SimpleMatch {
160+ pub start_pos: usize,
161+ pub end_pos: usize,
162+ pub val: String,
163+ }
164+
165+ impl SimpleMatch {
166+ pub fn from_match(m: Option<regex::Match<'_>>) -> Option<Self> {
167+ if let Some(m) = m {
168+ Some(Self {
169+ start_pos: m.start(),
170+ end_pos: m.end(),
171+ val: m.as_str().to_string(),
172+ })
173+ } else {
174+ None
175+ }
176+ }
177+
178+ pub fn to<T: std::str::FromStr>(&self) -> Option<T> {
179+ self.val.parse::<T>().ok()
180+ }
181+ }
182+ };
183+
184+ TokenStream::from(expanded)
185+}
+7-0tests/regex/Event.re
@@ -0,0 +1,7 @@
1+(?<start>\d{1,2}:\d{1,2})
2+.
3+(?<end>\d{1,2}:\d{1,2})
4+\s{0,2}
5+(?<ampm>[AMPM]{2})
6+.
7+(?<message>.+)
+16-0tests/samples/events.txt
@@ -0,0 +1,16 @@
1+๐Ÿง  Focused Dev + Growth Schedule
2+
3+Time Activity
4+7:30โ€“8:00 AM Wake up + hydrate + light stretch
5+8:00โ€“8:45 AM Gym (weights or intervals)
6+9:00โ€“9:30 AM Breakfast + cooldown + planning
7+9:30โ€“12:00PM Deep coding (core project)
8+12:00โ€“12:45PM Lunch + decompress
9+12:45โ€“2:30PM Coding or meetings if needed
10+2:30โ€“3:00 PM Break or walk
11+3:00โ€“5:00 PM Learning block (Rust, AI, etc)
12+5:00โ€“6:00 PM Chill / side project / cleanup
13+6:00โ€“7:00 PM Dinner + relax
14+7:00โ€“9:00 PM Optional: build/test/hack ideas
15+9:00โ€“10:00 PM Wind down (read, steam room, etc)
16+10:00โ€“10:30PM Journal + sleep prep
+2-0tests/test.rs
@@ -0,0 +1,2 @@
1+#[test]
2+pub fn