cargo-pystubgen 0.1.1

Cargo subcommand for xxxx
use quote::ToTokens;
use syn::Meta;
use std::path::PathBuf;

#[derive(Debug,Default,PartialEq, Eq)]
pub struct RustFunctionData{
    pub name: String,
    pub args: Vec<(String, String)>,
    pub return_type: String,
    pub attributes: Vec<String>,
    pub doc: String,
}

pub struct RustSrcData{
    pub functions: Vec<RustFunctionData>,
}

pub fn parse_rust_src_file(rust_src_file: &PathBuf) -> RustSrcData{
    let file_content = std::fs::read_to_string(rust_src_file).unwrap();
    let syn_file = syn::parse_str::<syn::File>(&file_content).unwrap();
    let functions = syn_file.items.iter().filter_map(|item|{
        if let syn::Item::Fn(item_fn) = item{
            Some(parse_function_data(item_fn))
        }
        else{
            None
        }
    }).collect();
    RustSrcData{
        functions: functions,
    }
}

pub fn parse_function_data(item: &syn::ItemFn) -> RustFunctionData{
    RustFunctionData{
        name: parse_function_name(item),
        args: parse_function_args(item),
        return_type: parse_function_return_type(item),
        attributes: parse_function_attributes(item),
        doc: parse_function_doc(item),
    }
}

pub fn parse_function_name(item: &syn::ItemFn) -> String{
    item.sig.ident.to_string()
}

pub fn parse_function_args(item: &syn::ItemFn) -> Vec<(String, String)>{
    item.sig.inputs.iter().map(|arg|match arg{
        syn::FnArg::Typed(arg) => (arg.pat.to_token_stream().to_string(), arg.ty.to_token_stream().to_string()),
        _ => ("self".to_string(), "self".to_string()),
    })
    .collect()
}

pub fn parse_function_return_type(item: &syn::ItemFn) -> String{
    match &item.sig.output {
        syn::ReturnType::Default => "()".to_string(),
        syn::ReturnType::Type(_, ty) => ty.to_token_stream().to_string(),
    }
}

pub fn parse_function_attributes(item: &syn::ItemFn) -> Vec<String> {
    item.attrs.iter()
        .filter_map(|attr| match &attr.meta {
            Meta::Path(path) if !attr.path().is_ident("doc") => {
                Some(path.segments.last().unwrap().ident.to_string())
            }
            _ => None,
        })
        .collect()
}


pub fn parse_function_doc(item: &syn::ItemFn) -> String{
    let mut doc = String::new();
    item.attrs.iter().for_each(|attr|{
        match &attr.meta {
            Meta::NameValue(name_value) =>match &name_value.value {
                syn::Expr::Lit(lit_str) => {
                    let doc_str = match &lit_str.lit {
                        syn::Lit::Str(lit_str) => lit_str.value(),
                        _ => "".to_string(),
                    };
                    doc.push_str(&doc_str);
                }
                _ => {}
            }
            Meta::Path(_) => {},
            Meta::List(_) => {},
        }
    });
    doc
}


#[cfg(test)]
mod tests {
    const TEST_CODE: &str = 
    "
    /// testcode add function
    /// second line
    #[pyfunction]
    #[testattribute]
    fn add(a: i32, b: i32) -> i32 {
        a + b
    }
    ";
    #[test]
    fn test_parse_function_return_type(){
        use super::*;
        let item: syn::ItemFn = syn::parse_str(TEST_CODE).unwrap();
        let function_data = parse_function_data(&item);

        assert_eq!(function_data.return_type, "i32");
    }

    #[test]
    fn test_parse_function_name(){
        use super::*;
        let item: syn::ItemFn = syn::parse_str(TEST_CODE).unwrap();
        let function_data = parse_function_data(&item);

        assert_eq!(function_data.name, "add");
    }

    #[test]
    fn test_parse_function_args(){
        use super::*;
        let item: syn::ItemFn = syn::parse_str(TEST_CODE).unwrap();
        let function_data = parse_function_data(&item);

        assert_eq!(function_data.args, vec![("a".to_string(), "i32".to_string()), ("b".to_string(), "i32".to_string())]);
    }

    #[test]
    fn test_parse_function_attributes(){
        use super::*;
        let item: syn::ItemFn = syn::parse_str(TEST_CODE).unwrap();
        let function_data = parse_function_data(&item);

        assert_eq!(function_data.attributes, vec!["pyfunction".to_string(), "testattribute".to_string()]);
    }

    #[test]
    fn test_parse_function_comments(){
        use super::*;
        let item: syn::ItemFn = syn::parse_str(TEST_CODE).unwrap();
        let function_data = parse_function_data(&item);

        assert_eq!(function_data.doc, " testcode add function second line");
    }

   
}