Skip to main content

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}