11//! Analyze the crate
2- use anyhow:: { Context , Result } ;
2+ use anyhow:: { anyhow, Context , Result } ;
3+ use cargo_metadata:: { MetadataCommand , Target } ;
34use serde:: { Deserialize , Serialize } ;
5+ use std:: path:: PathBuf ;
46
57use crate :: data_model:: { Crate , Enum , Function , Module , Struct } ;
68
79pub fn analyze_crate ( path : & str ) -> Result < AnalysisResult > {
810 // make the path absolute
911 // TODO we use dunce to canonicalize the path because otherwise there is issues with python's os.path.relpath on windows, but maybe we should fix this on the Python side
10- let path =
12+ let crate_dir =
1113 dunce:: canonicalize ( path) . context ( format ! ( "Error resolving crate path: {}" , path) ) ?;
14+ eprintln ! ( "running new analyzer" ) ;
1215 // check the path is a directory
13- if !path . is_dir ( ) {
14- return Err ( anyhow:: anyhow! ( format !(
16+ if !crate_dir . is_dir ( ) {
17+ return Err ( anyhow ! (
1518 "Crate path is not a directory: {}" ,
16- path . to_string_lossy( )
17- ) ) ) ;
19+ crate_dir . to_string_lossy( )
20+ ) ) ;
1821 }
1922 // check if Cargo.toml exists
20- let cargo_toml_path = path . join ( "Cargo.toml" ) ;
23+ let cargo_toml_path = crate_dir . join ( "Cargo.toml" ) ;
2124 if !cargo_toml_path. exists ( ) {
22- return Err ( anyhow:: anyhow! ( format !(
25+ return Err ( anyhow ! (
2326 "Cargo.toml does not exist in: {}" ,
24- path . to_string_lossy( )
25- ) ) ) ;
27+ crate_dir . to_string_lossy( )
28+ ) ) ;
2629 }
2730
28- // read the Cargo.toml and initialize the Crate struct
29- let contents = std:: fs:: read_to_string ( & cargo_toml_path) ?;
30- let cargo_toml: CargoToml = toml:: from_str ( & contents) . context ( format ! (
31- "Error parsing: {}" ,
32- cargo_toml_path. to_string_lossy( )
33- ) ) ?;
31+ // use `cargo_metadata` instead of implementing own TOML parser
32+ let metadata = MetadataCommand :: new ( )
33+ . manifest_path ( & cargo_toml_path)
34+ . exec ( )
35+ . context ( "Failed to run `cargo metadata`" ) ?;
3436
35- // check whether the crate is a library or binary
36- let ( crate_name, to_root) = if let Some ( lib) = cargo_toml. lib {
37- if cargo_toml. bin . is_some ( ) {
38- return Err ( anyhow:: anyhow!( format!(
39- "Both lib and bin sections in: {}" ,
40- path. to_string_lossy( )
41- ) ) ) ;
42- }
43- (
44- lib. name . unwrap_or ( cargo_toml. package . name ) ,
45- lib. path . unwrap_or ( "src/lib.rs" . to_string ( ) ) ,
46- )
47- } else if let Some ( bin) = cargo_toml. bin {
48- (
49- bin. name . unwrap_or ( cargo_toml. package . name ) ,
50- bin. path . unwrap_or ( "src/main.rs" . to_string ( ) ) ,
51- )
52- } else {
53- return Err ( anyhow:: anyhow!( format!(
54- "No lib or bin section in: {}" ,
55- path. to_string_lossy( )
56- ) ) ) ;
57- } ;
37+ let root_pkg = metadata
38+ . root_package ( )
39+ . ok_or_else ( || anyhow ! ( "`cargo metadata` returned no root package" ) ) ?;
40+
41+ // Prefer library target; fall back to the first binary target
42+ let root_target: & Target = root_pkg
43+ . targets
44+ . iter ( )
45+ . find ( |t| t. kind . contains ( & "lib" . into ( ) ) )
46+ . or_else ( || root_pkg. targets . iter ( ) . find ( |t| t. kind . contains ( & "bin" . into ( ) ) ) )
47+ . ok_or_else ( || anyhow ! ( "No lib or bin target defined in manifest" ) ) ?;
48+
49+ let crate_name = root_target. name . clone ( ) ;
50+ let root_module = PathBuf :: from ( & root_target. src_path ) ;
5851
5952 let mut result = AnalysisResult :: new ( Crate {
60- name : crate_name,
61- version : cargo_toml . package . version . clone ( ) ,
53+ name : crate_name. clone ( ) ,
54+ version : root_pkg . version . to_string ( ) , // workspace-aware
6255 } ) ;
6356
6457 // check existence of the root module
65- let root_module = path. join ( to_root) ;
6658 if !root_module. exists ( ) {
6759 return Ok ( result) ;
6860 }
6961
7062 // read the top-level module
7163 let content = std:: fs:: read_to_string ( & root_module) ?;
72- let ( module, structs, enums, functions) =
73- Module :: parse ( Some ( & root_module) , & [ & result. crate_ . name ] , & content) . context ( format ! (
74- "Error parsing module {}" ,
75- root_module. to_string_lossy( )
76- ) ) ?;
64+ let ( module, structs, enums, functions) = Module :: parse (
65+ Some ( & root_module) ,
66+ & [ & result. crate_ . name ] ,
67+ & content,
68+ )
69+ . context ( format ! (
70+ "Error parsing module {}" ,
71+ root_module. to_string_lossy( )
72+ ) ) ?;
73+
7774 let mut modules_to_read = module
7875 . declarations
7976 . iter ( )
@@ -91,24 +88,23 @@ pub fn analyze_crate(path: &str) -> Result<AnalysisResult> {
9188 result. enums . extend ( enums) ;
9289 result. functions . extend ( functions) ;
9390
94- // recursively find/read the public sub- modules
91+ // recursively find/read the public sub‑ modules
9592 let mut read_modules = vec ! [ ] ;
9693 while let Some ( ( parent_dir, module_name, parent) ) = modules_to_read. pop ( ) {
97- let ( module_path, submodule_dir) =
98- if parent_dir. join ( & module_name) . with_extension ( "rs" ) . exists ( ) {
99- (
100- parent_dir. join ( & module_name) . with_extension ( "rs" ) ,
101- parent_dir. join ( & module_name) ,
102- )
103- } else if parent_dir. join ( & module_name) . join ( "mod.rs" ) . exists ( ) {
104- (
105- parent_dir. join ( & module_name) . join ( "mod.rs" ) ,
106- parent_dir. to_path_buf ( ) ,
107- )
108- } else {
109- // TODO warn about missing module?
110- continue ;
111- } ;
94+ let ( module_path, submodule_dir) = if parent_dir. join ( & module_name) . with_extension ( "rs" ) . exists ( ) {
95+ (
96+ parent_dir. join ( & module_name) . with_extension ( "rs" ) ,
97+ parent_dir. join ( & module_name) ,
98+ )
99+ } else if parent_dir. join ( & module_name) . join ( "mod.rs" ) . exists ( ) {
100+ (
101+ parent_dir. join ( & module_name) . join ( "mod.rs" ) ,
102+ parent_dir. to_path_buf ( ) ,
103+ )
104+ } else {
105+ // TODO warn about missing module?
106+ continue ;
107+ } ;
112108
113109 if read_modules. contains ( & module_path) {
114110 continue ;
@@ -126,12 +122,12 @@ pub fn analyze_crate(path: &str) -> Result<AnalysisResult> {
126122 "Error parsing module {}" ,
127123 module_path. to_string_lossy( )
128124 ) ) ?;
125+
129126 modules_to_read. extend (
130127 module
131128 . declarations
132129 . iter ( )
133- . map ( |s| ( submodule_dir. clone ( ) , s. to_string ( ) , path. clone ( ) ) )
134- . collect :: < Vec < _ > > ( ) ,
130+ . map ( |s| ( submodule_dir. clone ( ) , s. to_string ( ) , path. clone ( ) ) ) ,
135131 ) ;
136132 result. modules . push ( module) ;
137133 result. structs . extend ( structs) ;
@@ -164,31 +160,6 @@ impl AnalysisResult {
164160 }
165161}
166162
167- #[ derive( Debug , Deserialize ) ]
168- struct CargoToml {
169- package : Package ,
170- bin : Option < Bin > ,
171- lib : Option < Lib > ,
172- }
173-
174- #[ derive( Debug , Deserialize ) ]
175- struct Package {
176- name : String ,
177- version : String ,
178- }
179-
180- #[ derive( Debug , Deserialize ) ]
181- struct Lib {
182- name : Option < String > ,
183- path : Option < String > ,
184- }
185-
186- #[ derive( Debug , Deserialize ) ]
187- struct Bin {
188- name : Option < String > ,
189- path : Option < String > ,
190- }
191-
192163#[ cfg( test) ]
193164mod tests {
194165 use super :: * ;
0 commit comments