1use std::{
2 borrow::Cow,
3 collections::HashMap,
4 fs::{self, File},
5 io::{BufWriter, Write},
6 path::Path,
7};
8
9use anyhow::bail;
10pub use stylance_core::Config;
11use stylance_core::ModifyCssResult;
12use walkdir::WalkDir;
13
14pub fn run(manifest_dir: &Path, config: &Config) -> anyhow::Result<()> {
15 println!("Running stylance");
16 run_silent(manifest_dir, config, |file_path| {
17 println!("{}", file_path.display())
18 })
19}
20
21pub fn run_silent(
22 manifest_dir: &Path,
23 config: &Config,
24 mut file_visit_callback: impl FnMut(&Path),
25) -> anyhow::Result<()> {
26 let mut modified_css_files = Vec::new();
27
28 for folder in &config.folders {
29 for (entry, meta) in WalkDir::new(manifest_dir.join(folder))
30 .into_iter()
31 .filter_map(|e| e.ok())
32 .filter_map(|entry| entry.metadata().ok().map(|meta| (entry, meta)))
33 {
34 if meta.is_file() {
35 let path_str = entry.path().to_string_lossy();
36 if config.extensions.iter().any(|ext| path_str.ends_with(ext)) {
37 file_visit_callback(entry.path());
38 modified_css_files.push(stylance_core::load_and_modify_css(
39 manifest_dir,
40 entry.path(),
41 config,
42 )?);
43 }
44 }
45 }
46 }
47
48 {
49 let mut map = HashMap::new();
51 for file in modified_css_files.iter() {
52 if let Some(previous_file) = map.insert(&file.hash, file) {
53 bail!(
54 "The following files had a hash collision:\n{}\n{}\nConsider increasing the hash_len setting.",
55 file.path.to_string_lossy(),
56 previous_file.path.to_string_lossy()
57 );
58 }
59 }
60 }
61
62 {
63 fn key(a: &ModifyCssResult) -> (&std::ffi::OsStr, &String) {
65 (
66 a.path.file_name().expect("should be a file"),
67 &a.normalized_path_str,
68 )
69 }
70 modified_css_files.sort_unstable_by(|a, b| key(a).cmp(&key(b)));
71 }
72
73 if let Some(output_file) = &config.output_file {
74 if let Some(parent) = output_file.parent() {
75 fs::create_dir_all(parent)?;
76 }
77
78 let mut file = BufWriter::new(File::create(output_file)?);
79
80 if let Some(scss_prelude) = &config.scss_prelude {
81 if output_file
82 .extension()
83 .filter(|ext| ext.to_string_lossy() == "scss")
84 .is_some()
85 {
86 file.write_all(scss_prelude.as_bytes())?;
87 file.write_all(b"\n\n")?;
88 }
89 }
90
91 file.write_all(
92 modified_css_files
93 .iter()
94 .map(|r| r.contents.as_ref())
95 .collect::<Vec<_>>()
96 .join("\n\n")
97 .as_bytes(),
98 )?;
99 }
100
101 if let Some(output_dir) = &config.output_dir {
102 let output_dir = output_dir.join("stylance");
103 fs::create_dir_all(&output_dir)?;
104
105 let entries = fs::read_dir(&output_dir)?;
106
107 for entry in entries {
108 let entry = entry?;
109 let file_type = entry.file_type()?;
110
111 if file_type.is_file() {
112 fs::remove_file(entry.path())?;
113 }
114 }
115
116 let mut new_files = Vec::new();
117 for modified_css in modified_css_files {
118 let extension = modified_css
119 .path
120 .extension()
121 .map(|e| e.to_string_lossy())
122 .filter(|e| e == "css")
123 .unwrap_or(Cow::from("scss"));
124
125 let new_file_name = format!(
126 "{}-{}.{extension}",
127 modified_css
128 .path
129 .file_stem()
130 .expect("This path should be a file")
131 .to_string_lossy(),
132 modified_css.hash
133 );
134
135 new_files.push(new_file_name.clone());
136
137 let file_path = output_dir.join(new_file_name);
138 let mut file = BufWriter::new(File::create(file_path)?);
139
140 if let Some(scss_prelude) = &config.scss_prelude {
141 if extension == "scss" {
142 file.write_all(scss_prelude.as_bytes())?;
143 file.write_all(b"\n\n")?;
144 }
145 }
146
147 file.write_all(modified_css.contents.as_bytes())?;
148 }
149
150 let mut file = File::create(output_dir.join("_index.scss"))?;
151 file.write_all(
152 new_files
153 .iter()
154 .map(|f| format!("@use \"{f}\";\n"))
155 .collect::<Vec<_>>()
156 .join("")
157 .as_bytes(),
158 )?;
159 }
160
161 Ok(())
162}