unique_type_id_derive/
lib.rs1#![recursion_limit = "128"]
2
3use fs2::FileExt;
4use proc_macro::TokenStream;
5use quote::quote;
6use std::collections::BTreeMap;
7use std::fs::File;
8use syn::parse_macro_input;
9
10static DEFAULT_TYPES_FILE_NAME: &str = "types.toml";
11static DEFAULT_ID_TYPE: &str = "u64";
12static DEFAULT_ID_START: &str = "0";
13
14type PairsMap = BTreeMap<String, u64>;
15
16#[proc_macro_derive(
111 UniqueTypeId,
112 attributes(UniqueTypeIdFile, UniqueTypeIdType, UniqueTypeIdStart)
113)]
114pub fn unique_type_id(input: TokenStream) -> TokenStream {
115 implement_type_id(input, unique_implementor)
116}
117
118fn read_file_into_string(file_name: &str) -> String {
119 use std::io::Read;
120
121 let mut f = match File::open(file_name) {
122 Ok(f) => f,
123 Err(_) => return String::default(),
124 };
125 f.lock_exclusive().expect("Unable to lock the file");
126 let mut contents = String::new();
127 f.read_to_string(&mut contents)
128 .expect("Unable to read the file");
129 fs2::FileExt::unlock(&f).expect("Unable to unlock the file");
130 contents
131}
132
133fn file_string_to_tree(file_contents: String) -> PairsMap {
134 let mut map = PairsMap::new();
135 file_contents
136 .split(&['\n', '\r'][..])
137 .filter_map(pair_from_line)
138 .for_each(|p| {
139 map.insert(p.0, p.1);
140 });
141 map
142}
143
144fn pair_from_line(line: &str) -> Option<(String, u64)> {
145 let mut pair = line.split('=');
146 let key = pair.next()?.to_owned();
147 let value = pair.next()?.parse::<u64>().ok()?;
148 Some((key, value))
149}
150
151fn append_pair_to_file(file_name: &str, record: &str, value: u64) {
152 use std::fs::OpenOptions;
153 use std::io::Write;
154
155 let mut f = OpenOptions::new()
156 .read(true)
157 .append(true)
158 .create(true)
159 .open(file_name)
160 .expect("Unable to create file");
161 f.lock_exclusive().expect("Unable to lock the file");
162 let contents = format!("{record}={value}\n");
163 f.write_all(contents.as_bytes())
164 .expect("Unable to write to the file");
165 fs2::FileExt::unlock(&f).expect("Unable to unlock the file");
166}
167
168fn gen_id(file_name: &str, record: &str, start: u64) -> u64 {
169 let pairs_map = file_string_to_tree(read_file_into_string(file_name));
170 match pairs_map.get(record) {
171 Some(record_id) => record_id.to_owned(),
172 None => {
173 let mut new_id = start;
174
175 loop {
176 if !pairs_map.values().any(|id| &new_id == id) {
177 break;
178 }
179 new_id += 1;
180 }
181
182 append_pair_to_file(file_name, record, new_id);
183 new_id
184 }
185 }
186}
187
188fn implement_type_id(
189 input: TokenStream,
190 implementor: fn(&syn::DeriveInput) -> TokenStream,
191) -> TokenStream {
192 let ast = parse_macro_input!(input as syn::DeriveInput);
193 implementor(&ast)
194}
195
196fn parse_attribute(attrs: &[syn::Attribute], name: &str, default: &str) -> String {
197 use quote::ToTokens;
198 use syn::spanned::Spanned;
199
200 attrs
201 .iter()
202 .find(|a| a.path().is_ident(name))
203 .map(|a| {
204 a.meta
205 .to_token_stream()
206 .into_iter()
207 .nth(2)
209 .ok_or_else(|| {
210 syn::Error::new(
211 a.span(),
212 format!(r#"The attribute should be in the format: `{name} = "{default}"`"#),
213 )
214 })
215 .unwrap()
216 .to_string()
217 .trim_matches('\"')
218 .to_owned()
219 })
220 .unwrap_or_else(|| default.to_string())
221}
222
223fn unique_implementor(ast: &syn::DeriveInput) -> TokenStream {
224 let name = &ast.ident;
225 let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
226 let types_file_name = parse_attribute(&ast.attrs, "UniqueTypeIdFile", DEFAULT_TYPES_FILE_NAME);
227 let id_type_name = parse_attribute(&ast.attrs, "UniqueTypeIdType", DEFAULT_ID_TYPE);
228 let gen_start: u64 = parse_attribute(&ast.attrs, "UniqueTypeIdStart", DEFAULT_ID_START)
229 .parse()
230 .unwrap();
231 let id_type = syn::parse_str::<syn::Type>(&id_type_name).unwrap();
232 let id = gen_id(&types_file_name, &ast.ident.to_string(), gen_start);
233
234 TokenStream::from(quote! {
236 impl #impl_generics unique_type_id::UniqueTypeId<#id_type> for #name #ty_generics #where_clause {
237 const TYPE_ID: unique_type_id::TypeId<#id_type> = unique_type_id::TypeId(#id as #id_type);
238
239 fn id() -> unique_type_id::TypeId<#id_type> {
240 <Self as unique_type_id::UniqueTypeId<#id_type>>::TYPE_ID
241 }
242 }
243
244 impl #impl_generics #name #ty_generics #where_clause {
245 const fn unique_type_id() -> unique_type_id::TypeId<#id_type> {
246 <Self as unique_type_id::UniqueTypeId<#id_type>>::TYPE_ID
247 }
248 }
249 })
250}