rs_hack/commands/
summary.rs1use std::path::PathBuf;
4
5use anyhow::{Context, Result};
6
7#[derive(Debug)]
8pub struct SummaryReport {
9 pub path: PathBuf,
10 pub module_doc: Option<String>,
11 pub public_items: Vec<String>,
12 pub struct_count: usize,
13 pub enum_count: usize,
14 pub type_alias_count: usize,
15 pub function_names: Vec<String>,
16 pub reexports: Vec<String>,
17}
18
19pub fn run(path: &PathBuf) -> Result<SummaryReport> {
20 let content = std::fs::read_to_string(path)
21 .with_context(|| format!("Failed to read file: {:?}", path))?;
22
23 let syntax =
24 syn::parse_file(&content).with_context(|| format!("Failed to parse file: {:?}", path))?;
25
26 let mut module_doc_parts: Vec<String> = Vec::new();
28 for attr in &syntax.attrs {
29 if let syn::AttrStyle::Inner(_) = attr.style
30 && attr.path().is_ident("doc")
31 && let Ok(syn::MetaNameValue {
32 value:
33 syn::Expr::Lit(syn::ExprLit {
34 lit: syn::Lit::Str(s),
35 ..
36 }),
37 ..
38 }) = attr.meta.require_name_value().cloned()
39 {
40 let text = s.value().trim().to_string();
41 if !text.is_empty() {
42 module_doc_parts.push(text);
43 }
44 }
45 }
46 let module_doc = if module_doc_parts.is_empty() {
47 None
48 } else {
49 Some(module_doc_parts.join(" "))
50 };
51
52 let mut public_items: Vec<String> = Vec::new();
53 let mut struct_count = 0usize;
54 let mut enum_count = 0usize;
55 let mut type_alias_count = 0usize;
56 let mut function_names: Vec<String> = Vec::new();
57 let mut reexports: Vec<String> = Vec::new();
58
59 for item in &syntax.items {
60 match item {
61 syn::Item::Struct(s) => {
62 struct_count += 1;
63 if is_public(&s.vis) {
64 public_items.push(s.ident.to_string());
65 }
66 }
67 syn::Item::Enum(e) => {
68 enum_count += 1;
69 if is_public(&e.vis) {
70 public_items.push(e.ident.to_string());
71 }
72 }
73 syn::Item::Type(t) => {
74 type_alias_count += 1;
75 if is_public(&t.vis) {
76 public_items.push(t.ident.to_string());
77 }
78 }
79 syn::Item::Fn(f) => {
80 function_names.push(f.sig.ident.to_string());
81 if is_public(&f.vis) {
82 public_items.push(f.sig.ident.to_string());
83 }
84 }
85 syn::Item::Trait(t) if is_public(&t.vis) => {
86 public_items.push(t.ident.to_string());
87 }
88 syn::Item::Const(c) if is_public(&c.vis) => {
89 public_items.push(c.ident.to_string());
90 }
91 syn::Item::Static(s) if is_public(&s.vis) => {
92 public_items.push(s.ident.to_string());
93 }
94 syn::Item::Mod(m) if is_public(&m.vis) => {
95 public_items.push(m.ident.to_string());
96 }
97 syn::Item::Use(u) if is_public(&u.vis) => {
98 let tokens = quote::quote!(#u);
99 reexports.push(tokens.to_string().replace(" :: ", "::"));
100 }
101 _ => {}
102 }
103 }
104
105 Ok(SummaryReport {
106 path: path.clone(),
107 module_doc,
108 public_items,
109 struct_count,
110 enum_count,
111 type_alias_count,
112 function_names,
113 reexports,
114 })
115}
116
117pub fn render(report: &SummaryReport) {
118 println!("Module: {}", report.path.display());
119
120 if report.public_items.is_empty() {
121 println!("Public items: (none)");
122 } else {
123 println!("Public items: {}", report.public_items.join(", "));
124 }
125
126 println!(
127 "Types: {} struct{}, {} enum{}, {} type alias{}",
128 report.struct_count,
129 if report.struct_count == 1 { "" } else { "s" },
130 report.enum_count,
131 if report.enum_count == 1 { "" } else { "s" },
132 report.type_alias_count,
133 if report.type_alias_count == 1 {
134 ""
135 } else {
136 "es"
137 },
138 );
139
140 if report.function_names.is_empty() {
141 println!("Functions: (none)");
142 } else {
143 println!("Functions: {}", report.function_names.join(", "));
144 }
145
146 if report.reexports.is_empty() {
147 println!("Re-exports: (none)");
148 } else {
149 for r in &report.reexports {
150 println!("Re-exports: {}", r);
151 }
152 }
153
154 match &report.module_doc {
155 Some(doc) => println!("Doc: {:?}", doc),
156 None => println!("Doc: (none)"),
157 }
158}
159
160const fn is_public(vis: &syn::Visibility) -> bool {
161 matches!(
162 vis,
163 syn::Visibility::Public(_) | syn::Visibility::Restricted(_)
164 )
165}