deb822-derive-0.2.0/.cargo_vcs_info.json0000644000000001530000000000100132740ustar { "git": { "sha1": "a227d3292e657f4d62e250875a42db80359414c4" }, "path_in_vcs": "deb822-derive" }deb822-derive-0.2.0/Cargo.toml0000644000000023120000000000100112710ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" name = "deb822-derive" version = "0.2.0" authors = ["Jelmer Vernooij "] build = false autobins = false autoexamples = false autotests = false autobenches = false description = "Derive macro for deb822 style paragraphs" homepage = "https://github.com/jelmer/deb822-lossless" readme = "README.md" keywords = [ "debian", "deb822", "rfc822", "lossless", "edit", ] categories = ["parser-implementations"] license = "Apache-2.0" repository = "https://github.com/jelmer/deb822-lossless" [lib] name = "deb822_derive" path = "src/lib.rs" proc-macro = true [dependencies.proc-macro2] version = "1.0.86" [dependencies.quote] version = "1.0.37" [dependencies.syn] version = "2" features = ["parsing"] deb822-derive-0.2.0/Cargo.toml.orig000064400000000000000000000007431046102023000147600ustar 00000000000000[package] name = "deb822-derive" description = "Derive macro for deb822 style paragraphs" authors = ["Jelmer Vernooij "] license = "Apache-2.0" version = "0.2.0" edition = "2021" repository.workspace = true homepage.workspace = true keywords = ["debian", "deb822", "rfc822", "lossless", "edit"] categories = ["parser-implementations"] [lib] proc-macro = true [dependencies] proc-macro2 = "1.0.86" quote = "1.0.37" syn = { version = "2", features = ["parsing"] } deb822-derive-0.2.0/README.md000064400000000000000000000007261046102023000133510ustar 00000000000000This crate provides a basic proc-macro for converting a Deb822Paragraph into a Rust struct and vice versa. You probably want to use the ``deb822_lossless`` crate instead, with the ``derive`` feature enabled. # Example ```rust use deb822_lossless::Deb822; #[derive(Deb822)] struct Foo { field1: String, field2: Option, } let paragraph: deb822::Deb822Paragraph = "field1: value1\nfield2: value2".parse().unwrap(); let foo: Foo = paragraph.into(); ``` deb822-derive-0.2.0/src/lib.rs000064400000000000000000000214671046102023000140020ustar 00000000000000extern crate proc_macro; use proc_macro::TokenStream; use quote::quote; use syn::spanned::Spanned; use syn::{parse_macro_input, DeriveInput}; use syn::{Type, TypePath}; fn is_option(ty: &syn::Type) -> bool { if let Type::Path(TypePath { path, .. }) = ty { if let Some(segment) = path.segments.last() { return segment.ident == "Option"; } } false } // Generate `from_paragraph`, ``to_paragraph`` methods for the annotated struct, i.e.: // // ```rust // #[derive(FromDeb822)] // struct X { // a: i32, // b: i32, // c: Option, // d: Vec, // #[deb822(field = "E")] // e: bool, // } // ``` // // will generate: // // ```rust // // impl FromDeb822Paragraph

for X { // fn from_paragraph(para: &P) -> Result { // Ok(Self { // a: para.get("a").ok_or_else(|| "missing field: a")?.parse().map_err(|e| format!("parsing field a: {}", e))?, // b: para.get("b").ok_or_else(|| "missing field: b")?.parse().map_err(|e| format!("parsing field b: {}", e))?, // c: para.get("c").map(|v| v.parse().map_err(|e| format!("parsing field c: {}", e))).transpose()?, // d: para.get("d").ok_or_else(|| "missing field: d")?.split_whitespace().map(|s| s.to_string()).collect(), // e: para.get("E").ok_or_else(|| "missing field: e")?.parse().map_err(|e| format!("parsing field E: {}", e))?, // }) // } // // And: // //// ```rust // #[derive(ToDeb822)] // struct X { // a: i32, // b: i32, // c: Option, // d: Vec, // #[deb822(field = "E")] // e: bool, // } // ``` // // will generate: // // ```rust // impl ToDeb822Paragraph

for X { // fn to_paragraph(&self) -> P { // let mut fields = Vec::<(String, String)>::new(); // fields.set("a", self.a.to_string()); // fields.set("b", self.b.to_string()); // if let Some(v) = &self.c { // fields.set("c", v.to_string()); // } // fields.set("d", self.d.join(" ")); // fields.set("E", self.e.to_string()); // deb822_lossless::Paragraph::from(fields) // } // // fn update_paragraph(&self, para: &mut deb822_lossless::Paragraph) { // para.set("a", &self.a.to_string()); // para.set("b", &self.b.to_string()); // if let Some(v) = &self.c { // para.set("c", &v.to_string()); // } else { // para.remove("c"); // } // para.set("d", &self.d.join(" ")); // para.set("E", &self.e.to_string()); // } // } // ``` struct FieldAttributes { field: Option, serialize_with: Option, deserialize_with: Option, } fn extract_field_attributes(attrs: &[syn::Attribute]) -> Result { let mut field = None; let mut serialize_with = None; let mut deserialize_with = None; for attr in attrs { if !attr.path().is_ident("deb822") { continue; } let name_values: syn::punctuated::Punctuated = attr.parse_args_with(syn::punctuated::Punctuated::parse_terminated)?; for nv in name_values { if nv.path.is_ident("field") { if let syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::Str(s), .. }) = nv.value { field = Some(s.value()); } else { return Err(syn::Error::new( nv.value.span(), "expected string literal in deb822 attribute", )); } } else if nv.path.is_ident("serialize_with") { if let syn::Expr::Path(s) = nv.value { serialize_with = Some(s); } else { return Err(syn::Error::new( nv.value.span(), "expected path in deb822 attribute", )); } } else if nv.path.is_ident("deserialize_with") { if let syn::Expr::Path(s) = nv.value { deserialize_with = Some(s); } else { return Err(syn::Error::new( nv.value.span(), "expected path in deb822 attribute", )); } } else { return Err(syn::Error::new( nv.span(), format!("unsupported attribute: {}", nv.path.get_ident().unwrap()), )); } } } Ok(FieldAttributes { field, serialize_with, deserialize_with, }) } #[proc_macro_derive(FromDeb822, attributes(deb822))] pub fn derive_from_deb822(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); let name = &input.ident; let s = if let syn::Data::Struct(s) = &input.data { s } else { panic!("FromDeb822 can only be derived for structs") }; let from_fields = s.fields.iter().map(|f| { let attrs = extract_field_attributes(&f.attrs).unwrap(); let ident = &f.ident; // Get key either from the #[deb822(field = "foo")] attribute, or derive it from the // field name let key = attrs.field.unwrap_or_else(||ident.as_ref().unwrap().to_string()); let deserialize_with = if let Some(deserialize_with) = attrs.deserialize_with { quote! { #deserialize_with } } else { quote! { std::str::FromStr::from_str } }; // Check if the field is optional or not let ty = &f.ty; let is_option = is_option(ty); if is_option { // Allow the field to be missing quote! { #ident: para.get(#key).map(|v| #deserialize_with(&v).map_err(|e| format!("parsing field {}: {}", #key, e))).transpose()? } } else { // The field is required quote! { #ident: #deserialize_with(¶.get(#key).ok_or_else(|| format!("missing field: {}", #key))?).map_err(|e| format!("parsing field {}: {}", #key, e))? } } }).collect::>(); let gen = quote! { impl deb822_lossless::FromDeb822Paragraph

for #name { fn from_paragraph(para: &P) -> Result { Ok(Self { #(#from_fields,)* }) } } }; gen.into() } #[proc_macro_derive(ToDeb822, attributes(deb822))] pub fn derive_to_deb822(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); let name = &input.ident; let s = if let syn::Data::Struct(s) = &input.data { s } else { panic!("Deb822 can only be derived for structs") }; let mut to_fields = vec![]; let mut update_fields = vec![]; for f in s.fields.iter() { let attrs = extract_field_attributes(&f.attrs).unwrap(); let ident = &f.ident; let key = attrs .field .unwrap_or_else(|| ident.as_ref().unwrap().to_string()); let serialize_with = if let Some(serialize_with) = attrs.serialize_with { quote! { #serialize_with } } else { quote! { ToString::to_string } }; let ty = &f.ty; let is_option = is_option(ty); to_fields.push(if is_option { quote! { if let Some(v) = &self.#ident { fields.push((#key.to_string(), #serialize_with(&v))); } } } else { quote! { fields.push((#key.to_string(), #serialize_with(&self.#ident))); } }); update_fields.push(if is_option { quote! { if let Some(v) = &self.#ident { para.set(#key, #serialize_with(&v).as_str()); } else { para.remove(#key); } } } else { quote! { para.set(#key, #serialize_with(&self.#ident).as_str()); } }); } let gen = quote! { impl deb822_lossless::ToDeb822Paragraph

for #name { fn to_paragraph(&self) -> P { let mut fields = Vec::<(String, String)>::new(); #(#to_fields)* fields.into_iter().collect() } fn update_paragraph(&self, para: &mut P) { #(#update_fields)* } } }; gen.into() }