pulsync_derive/lib.rs
1use std::collections::HashSet;
2
3use proc_macro::TokenStream;
4use syn::{parse::Parse, parse_macro_input, DeriveInput, LitStr};
5
6mod salt;
7mod task;
8
9/// Implementation for a Task to run with the scheduler
10///
11/// The derive will generate a `new` method to create a task,
12/// and wrap each fields inside `TaskState<T>`.
13///
14/// The `title` attribute, allow the user to choose a unique title.
15///
16/// # Example
17/// ```rust,ignore
18/// #[derive(Task)]
19/// #[title(format = "Task {self.id} {self.name}")]
20/// struct Task {
21/// id: TaskState<u32>,
22/// name: TaskState<String>,
23/// ...
24/// }
25///
26/// struct SecondTask {
27/// id: TaskState<u32>,
28/// ...
29/// }
30/// ```
31///
32/// In this example, the task implementation will look like this:
33/// ```rust,ignore
34/// impl Task {
35/// pub fn new(id: u32, name: String) -> Self {
36/// Self {
37/// id: TaskState::new(id),
38/// name: TaskState::new(name),
39/// }
40/// }
41/// }
42/// impl UniqueId for Task {}
43/// impl Task for Task {
44/// fn title(&self) -> String {
45/// format!("Task {} {}", &*self.id.read().unwrap(), &*self.name.read().unwrap())
46/// }
47/// }
48///
49/// impl SecondTask {
50/// pub fn new(id: u32) -> Self {
51/// Self {
52/// id: TaskState::new(id),
53/// }
54/// }
55/// }
56/// impl UniqueId for SecondTask {}
57/// impl Task for SecondTask {}
58/// ```
59#[proc_macro_derive(Task, attributes(title))]
60pub fn derive_task(input: TokenStream) -> TokenStream {
61 let ast = parse_macro_input!(input as DeriveInput);
62 task::impl_task_derive(&ast)
63}
64
65/// Derive macro used to implement the `Salt` trait
66///
67/// If the user need to use multiple tasks of the same type,
68/// the `salt` attribute can be used to define a unique salt.
69///
70/// The salt attribute is a string that can contain placeholders
71/// for the fields of the struct. The placeholders must be in the
72/// form of `{self.field_name}`.
73///
74/// # Example
75/// ```rust,ignore
76/// #[derive(Salt)]
77/// #[salt(format = "Task {self.id} {self.name}")]
78/// struct Task {
79/// id: TaskState<u32>,
80/// name: TaskState<String>,
81/// ...
82/// }
83///
84/// struct SecondTask {
85/// id: TaskState<u32>,
86/// ...
87/// }
88/// ```
89///
90/// In this example, the salt implementation will look like this:
91/// ```rust,ignore
92/// impl Salt for Task {
93/// fn salt(&self) -> String {
94/// format!("Task {} {}", &*self.id.read().unwrap(), &*self.name.read().unwrap())
95/// }
96/// }
97///
98/// impl Salt for SecondTask {}
99/// ```
100#[proc_macro_derive(Salt, attributes(salt))]
101pub fn derive_salt(input: TokenStream) -> TokenStream {
102 let ast = parse_macro_input!(input as DeriveInput);
103 salt::impl_salt_derive(&ast)
104}
105
106#[derive(Debug)]
107pub(crate) struct VariadicStringParams {
108 format_str: String,
109 field_names: HashSet<String>,
110}
111impl VariadicStringParams {
112 /// Parse and collect the field name without the 'self.' prefix
113 ///
114 /// Example:
115 /// ```ignore
116 /// // For the following field
117 /// "{self.counter}"
118 /// "counter" will be extracted
119 /// ```
120 fn extract_field_name(chars: &mut std::iter::Peekable<std::str::Chars>) -> Option<String> {
121 let mut field = String::new();
122
123 // Skip "self." prefix if present
124 if chars
125 .clone()
126 .collect::<String>()
127 .as_str()
128 .starts_with("self.")
129 {
130 for _ in 0..5 {
131 chars.next();
132 }
133 }
134
135 // Collect characters until closing brace
136 while let Some(&c) = chars.peek() {
137 if c == '}' {
138 chars.next();
139 return Some(field);
140 }
141 field.push(chars.next().unwrap());
142 }
143
144 None // Missing closing brace
145 }
146
147 /// Parse and collect the fields names without the 'self.' prefix
148 ///
149 /// Example:
150 /// ```ignore
151 /// // For the following string
152 /// "The title is {self.title} the task is {self.status}"
153 ///
154 /// "title" and "status" will be extracted
155 /// ```
156 fn parse_format_string(format_str: &str) -> HashSet<String> {
157 let mut field_names = HashSet::new();
158 let mut chars = format_str.chars().peekable();
159
160 while let Some(c) = chars.next() {
161 if c == '{' {
162 if let Some(field) = Self::extract_field_name(&mut chars) {
163 if !field.is_empty() {
164 field_names.insert(field);
165 }
166 }
167 }
168 }
169
170 field_names
171 }
172}
173
174impl Parse for VariadicStringParams {
175 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
176 let format_lit = input.parse::<LitStr>()?;
177 let format_str = format_lit.value();
178 let field_names = Self::parse_format_string(&format_str);
179
180 Ok(VariadicStringParams {
181 format_str,
182 field_names,
183 })
184 }
185}