1#![allow(clippy::missing_errors_doc)]
2
3use crate::syn::expand_cfg_if;
4use crate::syn::find_cfg_items;
5use crate::syn::find_cfg_mod;
6use crate::transform::simplified_expr;
7
8use bool_logic::cfg::ast::Expr as CfgExpr;
9use bool_logic::cfg::ast::all as cfg_all;
10use bool_logic::cfg::ast::any as cfg_any;
11
12use std::collections::HashMap;
13use std::fs;
14use std::ops::Not as _;
15
16use syn::File;
17use syn::Ident;
18
19use anyhow::Result;
20use camino::{Utf8Path, Utf8PathBuf};
21use log::debug;
22use regex::RegexSet;
23use stdx::default::default;
24use stdx::iter::map_collect_vec;
25
26pub struct CfgItem {
27 pub cfg: CfgExpr,
28 pub name: String,
29}
30
31pub fn search(libc: impl AsRef<Utf8Path>, re: &RegexSet) -> Result<Vec<CfgItem>> {
32 let items = search_items(libc.as_ref(), re)?;
33
34 Ok(map_collect_vec(items, |(cfg, name)| CfgItem {
35 cfg,
36 name: name.to_string(),
37 }))
38}
39
40fn search_items(libc: &Utf8Path, re: &RegexSet) -> Result<Vec<(CfgExpr, Ident)>> {
41 let mut ctx = DfsContext {
42 re,
43 items: default(),
44 };
45
46 {
47 let dir = libc.join("src");
48 let fs_path = libc.join("src/lib.rs");
49
50 let ast = parse_file(&fs_path)?;
51 for (cfg, name) in find_mod_in_file(&ast) {
52 let fs_path = resolve_fs_path(&dir, &name);
53 dfs(&mut ctx, &fs_path, &name, &cfg)?;
54 }
55 }
56
57 debug!("aggregating items");
58 Ok(aggregate_items(ctx.items))
59}
60
61fn parse_file(fs_path: &Utf8Path) -> Result<File> {
62 let s = fs::read_to_string(fs_path)?;
63 let mut ast = syn::parse_file(&s)?;
64 expand_cfg_if(&mut ast);
65 Ok(ast)
66}
67
68fn find_mod_in_file(ast: &File) -> Vec<(CfgExpr, Ident)> {
69 let mut map: HashMap<Ident, Vec<CfgExpr>> = default();
70
71 for (cfg, name) in find_cfg_mod(ast) {
72 map.entry(name).or_default().push(cfg);
73 }
74
75 aggregate_items(map)
76}
77
78fn aggregate_items(map: HashMap<Ident, Vec<CfgExpr>>) -> Vec<(CfgExpr, Ident)> {
79 let mut ans = map_collect_vec(map, |(name, exprs)| (simplified_expr(cfg_any(exprs)), name));
80 ans.sort_by(|lhs, rhs| lhs.1.cmp(&rhs.1));
81 ans
82}
83
84fn resolve_fs_path(dir: &Utf8Path, mod_name: &Ident) -> Utf8PathBuf {
85 let first = dir.join(format!("{mod_name}.rs"));
86 let second = dir.join(format!("{mod_name}/mod.rs"));
87
88 if first.exists() {
89 assert!(second.exists().not());
90 first
91 } else {
92 second
93 }
94}
95
96struct DfsContext<'a> {
97 re: &'a RegexSet,
98 items: HashMap<Ident, Vec<CfgExpr>>,
99}
100
101fn dfs(
102 ctx: &mut DfsContext,
103 fs_path: &Utf8Path,
104 mod_name: &Ident,
105 mod_cfg: &CfgExpr,
106) -> Result<()> {
107 debug!("dfs: mod {mod_name:<16} at {fs_path:<80}: {mod_cfg}");
108
109 let dir = fs_path.parent().unwrap();
110
111 let ast = parse_file(fs_path)?;
112
113 for (cfg, name) in find_cfg_items(&ast, ctx.re) {
114 let item_cfg = join_item_cfg(mod_cfg, cfg);
115 ctx.items.entry(name).or_default().push(item_cfg);
116 }
117
118 for (cfg, name) in find_mod_in_file(&ast) {
119 let mod_name = &name;
120 let fs_path = resolve_fs_path(dir, mod_name);
121 let mod_cfg = join_item_cfg(mod_cfg, cfg);
122
123 dfs(ctx, &fs_path, mod_name, &mod_cfg)?;
124 }
125
126 Ok(())
127}
128
129fn join_item_cfg(mod_cfg: &CfgExpr, item_cfg: CfgExpr) -> CfgExpr {
130 simplified_expr(cfg_all((mod_cfg.clone(), item_cfg)))
131}