deb822-derive-0.3.1/.cargo_vcs_info.json0000644000000001530000000000100132760ustar { "git": { "sha1": "6024fe094dd9d5e09dc4b77619fc1ef492a0aa08" }, "path_in_vcs": "deb822-derive" }deb822-derive-0.3.1/Cargo.lock0000644000000021370000000000100112550ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 4 [[package]] name = "deb822-derive" version = "0.3.1" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "proc-macro2" version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" dependencies = [ "proc-macro2", ] [[package]] name = "syn" version = "2.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "unicode-ident" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" deb822-derive-0.3.1/Cargo.toml0000644000000024200000000000100112730ustar # 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.3.1" authors = ["Jelmer Vernooij "] build = false autolib = 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" [badges.maintenance] status = "actively-maintained" [lib] name = "deb822_derive" path = "src/lib.rs" proc-macro = true [dependencies.proc-macro2] version = "1.0.101" [dependencies.quote] version = "1.0.41" [dependencies.syn] version = "2" features = ["parsing"] deb822-derive-0.3.1/Cargo.toml.orig000064400000000000000000000010371046102023000147570ustar 00000000000000[package] name = "deb822-derive" description = "Derive macro for deb822 style paragraphs" authors = ["Jelmer Vernooij "] license = "Apache-2.0" version = "0.3.1" 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.101" quote = "1.0.41" syn = { version = "2", features = ["parsing"] } [badges] maintenance = { status = "actively-maintained" } deb822-derive-0.3.1/README.md000064400000000000000000000023571046102023000133550ustar 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(); ``` # Field Formatting Attributes The derive macros support field formatting attributes to control how fields are serialized: ## `single_line` Forces the field value to be on a single line. ```rust #[derive(ToDeb822)] struct Package { #[deb822(field = "Package", single_line)] name: String, } ``` ## `multi_line` Ensures continuation lines start with a space character, following deb822 format conventions. ```rust #[derive(ToDeb822)] struct Package { #[deb822(field = "Description", multi_line)] description: String, } ``` ## `folded` Strips leading and trailing whitespace from each line and joins them with spaces, implementing RFC 822 folding behavior. ```rust #[derive(ToDeb822)] struct Package { #[deb822(field = "Depends", folded)] depends: String, } ``` deb822-derive-0.3.1/src/lib.rs000064400000000000000000000245441046102023000140030ustar 00000000000000extern crate proc_macro; use proc_macro::TokenStream; use quote::quote; 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 code to format a field value based on its FieldType fn apply_field_formatting( field_type: Option, field_name: &str, ) -> proc_macro2::TokenStream { match field_type { Some(FieldType::SingleLine) => quote! { deb822_fast::convert::format_single_line(&value, #field_name) }, Some(FieldType::MultiLine) => quote! { deb822_fast::convert::format_multi_line(&value) }, Some(FieldType::Folded) => quote! { deb822_fast::convert::format_folded(&value) }, None => quote! { value }, } } // 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_fast::Paragraph::from(fields) // } // // fn update_paragraph(&self, para: &mut deb822_fast::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()); // } // } // ``` #[derive(Debug, Clone, Copy, PartialEq, Eq)] enum FieldType { SingleLine, MultiLine, Folded, } struct FieldAttributes { field: Option, serialize_with: Option, deserialize_with: Option, field_type: Option, } fn extract_field_attributes(attrs: &[syn::Attribute]) -> Result { let mut field = None; let mut serialize_with = None; let mut deserialize_with = None; let mut field_type = None; for attr in attrs { if !attr.path().is_ident("deb822") { continue; } // Parse the attribute arguments attr.parse_nested_meta(|meta| { if meta.path.is_ident("field") { let value = meta.value()?; let s: syn::LitStr = value.parse()?; field = Some(s.value()); Ok(()) } else if meta.path.is_ident("serialize_with") { let value = meta.value()?; let path: syn::ExprPath = value.parse()?; serialize_with = Some(path); Ok(()) } else if meta.path.is_ident("deserialize_with") { let value = meta.value()?; let path: syn::ExprPath = value.parse()?; deserialize_with = Some(path); Ok(()) } else if meta.path.is_ident("folded") { if field_type.is_some() { return Err(meta.error( "only one of 'folded', 'single_line', or 'multi_line' can be specified", )); } field_type = Some(FieldType::Folded); Ok(()) } else if meta.path.is_ident("single_line") { if field_type.is_some() { return Err(meta.error( "only one of 'folded', 'single_line', or 'multi_line' can be specified", )); } field_type = Some(FieldType::SingleLine); Ok(()) } else if meta.path.is_ident("multi_line") { if field_type.is_some() { return Err(meta.error( "only one of 'folded', 'single_line', or 'multi_line' can be specified", )); } field_type = Some(FieldType::MultiLine); Ok(()) } else { Err(meta.error(format!( "unsupported attribute: {}", meta.path.get_ident().unwrap() ))) } })?; } Ok(FieldAttributes { field, serialize_with, deserialize_with, field_type, }) } #[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_fast::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 field_type = attrs.field_type; let format_value = apply_field_formatting(field_type, &key); let ty = &f.ty; let is_option = is_option(ty); to_fields.push(if is_option { quote! { if let Some(v) = &self.#ident { let value = #serialize_with(&v); let formatted = #format_value; fields.push((#key.to_string(), formatted)); } } } else { quote! { let value = #serialize_with(&self.#ident); let formatted = #format_value; fields.push((#key.to_string(), formatted)); } }); update_fields.push(if is_option { quote! { if let Some(v) = &self.#ident { let value = #serialize_with(&v); let formatted = #format_value; para.set(#key, formatted.as_str()); } else { para.remove(#key); } } } else { quote! { let value = #serialize_with(&self.#ident); let formatted = #format_value; para.set(#key, formatted.as_str()); } }); } let gen = quote! { impl deb822_fast::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() }