11use anyhow:: Result ;
2+ use reqwest;
3+ use std:: fs;
4+ use std:: path:: Path ;
5+ use tar:: Archive ;
6+ use flate2:: read:: GzDecoder ;
7+ use syn:: { File , Item } ;
8+ use tokio:: fs as tokio_fs;
29
310/// Represents filters for item listing.
411#[ derive( Debug ) ]
@@ -8,70 +15,55 @@ pub struct ItemListFilters {
815 pub module : Option < String > ,
916}
1017
18+ /// Utility function to download and cache crate source.
19+ async fn download_and_cache_crate ( crate_name : & str , version : & str ) -> Result < String > {
20+ let cache_dir = Path :: new ( "./cache" ) ;
21+ let crate_dir = cache_dir. join ( format ! ( "{}-{}" , crate_name, version) ) ;
22+
23+ if crate_dir. exists ( ) {
24+ return Ok ( crate_dir. to_string_lossy ( ) . to_string ( ) ) ;
25+ }
26+
27+ let url = format ! ( "https://crates.io/api/v1/crates/{}/{}/download" , crate_name, version) ;
28+ let response = reqwest:: get ( & url) . await ?;
29+ let tarball = response. bytes ( ) . await ?;
30+
31+ fs:: create_dir_all ( & cache_dir) ?;
32+ let tar_gz = GzDecoder :: new ( & * tarball) ;
33+ let mut archive = Archive :: new ( tar_gz) ;
34+ archive. unpack ( & cache_dir) ?;
35+
36+ Ok ( crate_dir. to_string_lossy ( ) . to_string ( ) )
37+ }
38+
1139/// Stub for the crate item enumeration tool.
1240/// This will use rust-analyzer to enumerate items in a crate.
1341pub async fn list_crate_items (
1442 crate_name : & str ,
1543 version : & str ,
1644 filters : Option < ItemListFilters > ,
1745) -> Result < String > {
18- // 🦨 skunky: Implementation pending. Will use rust-analyzer APIs.
19- Ok ( format ! (
20- "Stub: list_crate_items for crate: {}, version: {}, filters: {:?}" ,
21- crate_name, version, filters
22- ) )
23- }
46+ let crate_path = download_and_cache_crate ( crate_name, version) . await ?;
47+ let mut items = Vec :: new ( ) ;
2448
25- #[ cfg( test) ]
26- mod tests {
27- use super :: * ;
28- use tokio;
49+ for entry in fs:: read_dir ( crate_path) ? {
50+ let entry = entry?;
51+ let path = entry. path ( ) ;
52+ if path. extension ( ) . and_then ( |ext| ext. to_str ( ) ) == Some ( "rs" ) {
53+ let content = fs:: read_to_string ( & path) ?;
54+ let parsed_file: File = syn:: parse_file ( & content) ?;
2955
30- #[ tokio:: test]
31- async fn test_basic_call_returns_stub ( ) {
32- let result = list_crate_items ( "serde" , "1.0.0" , None ) . await . unwrap ( ) ;
33- assert ! ( result. contains( "Stub: list_crate_items for crate: serde, version: 1.0.0" ) , "Stub output missing expected text" ) ;
56+ for item in parsed_file. items {
57+ match item {
58+ Item :: Struct ( _) if filters. as_ref ( ) . map_or ( true , |f| f. item_type . as_deref ( ) == Some ( "struct" ) ) => items. push ( format ! ( "{:?}" , item) ) ,
59+ Item :: Enum ( _) if filters. as_ref ( ) . map_or ( true , |f| f. item_type . as_deref ( ) == Some ( "enum" ) ) => items. push ( format ! ( "{:?}" , item) ) ,
60+ Item :: Trait ( _) if filters. as_ref ( ) . map_or ( true , |f| f. item_type . as_deref ( ) == Some ( "trait" ) ) => items. push ( format ! ( "{:?}" , item) ) ,
61+ Item :: Fn ( _) if filters. as_ref ( ) . map_or ( true , |f| f. item_type . as_deref ( ) == Some ( "fn" ) ) => items. push ( format ! ( "{:?}" , item) ) ,
62+ _ => { }
63+ }
64+ }
65+ }
3466 }
3567
36- #[ tokio:: test]
37- async fn test_with_item_type_filter ( ) {
38- let filters = ItemListFilters {
39- item_type : Some ( "struct" . to_string ( ) ) ,
40- visibility : None ,
41- module : None ,
42- } ;
43- let result = list_crate_items ( "serde" , "1.0.0" , Some ( filters) ) . await . unwrap ( ) ;
44- assert ! ( result. contains( "filters: Some" ) , "Stub output missing filters" ) ;
45- assert ! ( result. contains( "struct" ) , "Stub output missing item_type" ) ;
46- }
47-
48- #[ tokio:: test]
49- async fn test_with_visibility_filter ( ) {
50- let filters = ItemListFilters {
51- item_type : None ,
52- visibility : Some ( "pub" . to_string ( ) ) ,
53- module : None ,
54- } ;
55- let result = list_crate_items ( "serde" , "1.0.0" , Some ( filters) ) . await . unwrap ( ) ;
56- assert ! ( result. contains( "filters: Some" ) , "Stub output missing filters" ) ;
57- assert ! ( result. contains( "pub" ) , "Stub output missing visibility" ) ;
58- }
59-
60- #[ tokio:: test]
61- async fn test_with_module_filter ( ) {
62- let filters = ItemListFilters {
63- item_type : None ,
64- visibility : None ,
65- module : Some ( "serde::de" . to_string ( ) ) ,
66- } ;
67- let result = list_crate_items ( "serde" , "1.0.0" , Some ( filters) ) . await . unwrap ( ) ;
68- assert ! ( result. contains( "filters: Some" ) , "Stub output missing filters" ) ;
69- assert ! ( result. contains( "serde::de" ) , "Stub output missing module filter" ) ;
70- }
71-
72- #[ tokio:: test]
73- async fn test_invalid_crate_name ( ) {
74- let result = list_crate_items ( "not_a_real_crate" , "0.0.1" , None ) . await . unwrap ( ) ;
75- assert ! ( result. contains( "not_a_real_crate" ) , "Stub output missing invalid crate name" ) ;
76- }
68+ Ok ( items. join ( "\n " ) )
7769}
0 commit comments