Skip to main content

Maintain/Eliminate/
Process.rs

1//=============================================================================//
2// File Path: Element/Maintain/Source/Eliminate/Process.rs
3//=============================================================================//
4// Module: Process - File discovery and transformation orchestration
5//=============================================================================//
6
7use std::{
8	fs,
9	path::{Path, PathBuf},
10};
11
12use globset::{Glob, GlobSet, GlobSetBuilder};
13use walkdir::WalkDir;
14
15use super::{Definition, Error, Transform};
16
17/// Discover Rust source files matching `Pattern` under `Root`, run the
18/// elimination transform on each, and write back the result unless
19/// `Options.DryRun` is set.
20///
21/// When `Options.Reformat` is `false` (the default) the preserve-layout path
22/// is used: only the inlined binding sites are rewritten; comments, blank
23/// lines, and indentation style are kept verbatim.
24///
25/// When `Options.Reformat` is `true` the whole file is reformatted with
26/// `prettyplease` after inlining (the previous unconditional behaviour).
27///
28/// Returns aggregate [`Definition::Stats`] describing what was processed.
29pub fn Process(Root:&Path, Pattern:&str, Options:&Definition::Options) -> Error::Result<Definition::Stats> {
30	let mut Stats = Definition::Stats::default();
31
32	let GlobMatcher = BuildGlobSet(Pattern)?;
33
34	let Files = CollectFiles(Root, &GlobMatcher);
35
36	for FilePath in Files {
37		Stats.FilesProcessed += 1;
38
39		ProcessFile(&FilePath, Options, &mut Stats)?;
40	}
41
42	Ok(Stats)
43}
44
45// ---------------------------------------------------------------------------
46// Internals
47// ---------------------------------------------------------------------------
48
49fn BuildGlobSet(Pattern:&str) -> Error::Result<GlobSet> {
50	let mut Builder = GlobSetBuilder::new();
51
52	Builder.add(Glob::new(Pattern)?);
53
54	Ok(Builder.build()?)
55}
56
57fn CollectFiles(Root:&Path, GlobMatcher:&GlobSet) -> Vec<PathBuf> {
58	if Root.is_file() {
59		return vec![Root.to_path_buf()];
60	}
61
62	WalkDir::new(Root)
63		.follow_links(false)
64		.into_iter()
65		.filter_map(|Entry| Entry.ok())
66		.filter(|Entry| Entry.file_type().is_file())
67		.filter(|Entry| {
68			let RelativePath = Entry.path().strip_prefix(Root).unwrap_or(Entry.path());
69
70			GlobMatcher.is_match(RelativePath)
71		})
72		.map(|Entry| Entry.path().to_path_buf())
73		.collect()
74}
75
76fn ProcessFile(FilePath:&Path, Options:&Definition::Options, Stats:&mut Definition::Stats) -> Error::Result<()> {
77	let Source = fs::read_to_string(FilePath)?;
78
79	// Choose the transform path based on Options.Reformat.
80	// - Reformat:false (default): text-level substitution, layout preserved.
81	// - Reformat:true: full prettyplease reformat (previous behaviour).
82	let TransformResult = if Options.Reformat {
83		Transform::Run(&Source, Options)
84	} else {
85		Transform::RunPreserve(&Source, Options)
86	}
87	.map_err(|E| {
88		if let Error::Error::Parse { Source: Src, .. } = E {
89			Error::Error::Parse { Path:FilePath.display().to_string(), Source:Src }
90		} else {
91			E
92		}
93	})?;
94
95	let Some(Transformed) = TransformResult else {
96		return Ok(());
97	};
98
99	Stats.FilesModified += 1;
100
101	// Count bindings inlined: count how many fewer `let ` lines the output has.
102	let Before = Source.lines().filter(|L| L.trim_start().starts_with("let ")).count();
103
104	let After = Transformed.lines().filter(|L| L.trim_start().starts_with("let ")).count();
105
106	Stats.BindingsInlined += Before.saturating_sub(After);
107
108	if Options.Verbose {
109		log::info!("{}: {} binding(s) inlined", FilePath.display(), Before.saturating_sub(After));
110	}
111
112	if Options.DryRun {
113		log::info!("--- {} (dry-run, not written) ---\n{}", FilePath.display(), Transformed);
114	} else {
115		fs::write(FilePath, Transformed)?;
116	}
117
118	Ok(())
119}