serde_doc/
extract.rs

1use std::path::Path;
2
3use anyhow::{ Context as _, Result};
4
5use crate::{Context, FieldUnit, FileUnit, StructUnit};
6
7pub fn process_path<S: AsRef<Path>>(context: &mut Context, p: S) -> Result<()> {
8    let path = p.as_ref();
9    if path.is_file() && path.extension().map_or(false, |ext| ext == "rs") {
10
11        let code = std::fs::read_to_string(path)?;
12        let unit = process_code(code).with_context(|| format!("failed to parse file: {:?}", path))?;
13        context.files.push(unit);
14    } else if path.is_dir() {
15        for entry in std::fs::read_dir(path)? {
16            let entry = entry?;
17            let entry_path = entry.path();
18            if entry_path.is_file() {
19                process_path(context, entry.path())?;
20            } else if entry_path.is_dir() {
21                process_path(context, entry.path())?;
22            }
23        }
24    }
25    Ok(())
26}
27
28fn process_code<S: AsRef<str>>(code: S) -> Result<FileUnit> {
29    let tree: syn::File = syn::parse_str(code.as_ref()).context("failed to parse code")?;
30    let mut unit = FileUnit::new();
31    for item in &tree.items {
32        if let syn::Item::Struct(item_struct) = item {
33            unit.structs.push(process_struct(item_struct).context("failed to parse struct")?);
34        }
35    }
36    Ok(unit)
37}
38
39fn process_struct(item_struct: &syn::ItemStruct) -> Result<StructUnit> {
40    let mut struct_unit = StructUnit::new(
41        item_struct.ident.to_string(),
42    );
43
44    if let Some(comment) = item_struct.attrs.iter().find(|attr| attr.path().is_ident("doc")) {
45        if let Ok(name) = comment.meta.require_name_value() {
46            if let syn::Expr::Lit(expr_list) = &name.value {
47                if let syn::Lit::Str(lit) = &expr_list.lit {
48                    struct_unit.doc = Some(lit.value());
49                }
50            }
51        }
52    }
53
54    if let syn::Fields::Named(fields) = &item_struct.fields {
55        for field in &fields.named {
56            let name = field.ident.as_ref().unwrap().to_string();
57            let ty = match &field.ty {
58                syn::Type::Path(type_path) => type_path.path.segments.last().unwrap().ident.to_string(),
59                _ => "Unknown".to_string(),
60            };
61            let doc:Option<String> = field.attrs.iter()
62            .find(|attr| attr.path().is_ident("doc"))
63            .and_then(|attr|{
64                if let Ok(name) = attr.meta.require_name_value() {
65                    if let syn::Expr::Lit(expr_list) = &name.value {
66                        if let syn::Lit::Str(lit) = &expr_list.lit {
67                            return Some(lit.value());
68                        }
69                    }
70                    
71                }
72               None
73            });
74            
75            
76            struct_unit.fields.push(FieldUnit::new (
77                name,
78                ty,
79                doc
80            ));
81        }
82    }
83
84    for attr in &item_struct.attrs {
85        if attr.path().is_ident("derive") {
86            attr.parse_nested_meta(|meta| {
87                if let Some(ident) = meta.path.get_ident() {
88                    struct_unit.derive.push(ident.to_string());
89                } 
90
91                Ok(())
92            })?;            
93        }
94    }
95
96    Ok(struct_unit)
97}
98
99
100
101#[cfg(test)]
102mod test {
103
104    use super::*;
105
106    #[test]
107    fn test_parse_struct() -> Result<()> {
108        let code = r#"
109#[derive(Serialize, Deserialize, Debug)]
110struct Point {
111    x: i32,
112    y: i32,
113}
114"#;
115        let unit = process_code(code)?;
116
117        assert_eq!(unit.structs.len(), 1);
118        let struct_unit = &unit.structs[0];
119        assert_eq!(struct_unit.name, "Point");
120        assert_eq!(struct_unit.fields.len(), 2);
121        assert_eq!(struct_unit.fields[0].name, "x");
122        assert_eq!(struct_unit.fields[0].ty, "i32");
123        assert_eq!(struct_unit.fields[1].name, "y");
124        assert_eq!(struct_unit.fields[1].ty, "i32");
125        assert_eq!(struct_unit.derive.len(), 3);
126        assert_eq!(struct_unit.derive[0], "Serialize");
127        Ok(())
128    }
129
130    #[test]
131    fn test_parse_struct_with_comment() -> Result<()> {
132        let code = r#"
133#[derive(Serialize, Deserialize, Debug)]
134/// This is a point struct
135struct Point {
136    /// The x coordinate
137    x: i32,
138    /// The y coordinate
139    y: i32, 
140}
141"#;
142        let unit = process_code(code)?;
143
144        assert_eq!(unit.structs.len(), 1);
145        let struct_unit = &unit.structs[0];
146        assert_eq!(struct_unit.name, "Point");
147        assert_eq!(struct_unit.fields.len(), 2);
148        assert_eq!(struct_unit.fields[0].name, "x");
149        assert_eq!(struct_unit.fields[0].ty, "i32");
150        assert_eq!(struct_unit.fields[1].name, "y");
151        assert_eq!(struct_unit.fields[1].ty, "i32");
152        assert_eq!(struct_unit.derive.len(), 3);
153        assert_eq!(struct_unit.derive[0], "Serialize");
154        assert_eq!(struct_unit.doc, Some(" This is a point struct".to_string()));
155        assert_eq!(struct_unit.fields[0].doc, Some(" The x coordinate".to_string()));
156        assert_eq!(struct_unit.fields[1].doc, Some(" The y coordinate".to_string()));
157
158        Ok(())
159    }
160}