1use std::{
2 collections::{BTreeMap, BTreeSet, HashMap, HashSet},
3 fs,
4 path::{Path, PathBuf},
5};
6
7use anyhow::Context;
8use itertools::Itertools;
9use typeshare_model::prelude::*;
10
11use crate::{args::OutputLocation, parser::ParsedData, topsort::topsort};
12
13pub fn write_output<'c>(
14 lang: &impl Language<'c>,
15 crate_parsed_data: HashMap<Option<CrateName>, ParsedData>,
16 dest: &OutputLocation<'_>,
17) -> anyhow::Result<()> {
18 match dest {
19 OutputLocation::File(file) => {
20 let mut parsed_data = crate_parsed_data
22 .into_values()
23 .reduce(|mut data, new_data| {
24 data.merge(new_data);
25 data
26 })
27 .context("called `write_output` with no data")?;
28
29 parsed_data.sort_contents();
30 write_single_file(lang, file, &parsed_data)
31 }
32 OutputLocation::Folder(directory) => {
33 let crate_parsed_data = crate_parsed_data
36 .into_iter()
37 .map(|(crate_name, mut data)| match crate_name {
38 Some(crate_name) => {
39 data.sort_contents();
40 Ok((crate_name, data))
41 }
42 None => anyhow::bail!(
43 "got files with unknown crates; all files \
44 must be in crates in multi-file mode"
45 ),
46 })
47 .try_collect()?;
48
49 write_multiple_files(lang, directory, &crate_parsed_data)
50 }
51 }
52}
53
54pub fn write_multiple_files<'c>(
56 lang: &impl Language<'c>,
57 output_folder: &Path,
58 crate_parsed_data: &HashMap<CrateName, ParsedData>,
59) -> anyhow::Result<()> {
60 let mut output_files = Vec::with_capacity(crate_parsed_data.len());
61
62 for (crate_name, parsed_data) in crate_parsed_data {
63 let file_path = output_folder.join(&lang.output_filename_for_crate(&crate_name));
64
65 let mut output = Vec::new();
66
67 generate_types(
68 lang,
69 &mut output,
70 parsed_data,
71 FilesMode::Multi(&crate_name),
72 )
73 .with_context(|| format!("error generating typeshare types for crate {crate_name}"))?;
74
75 check_write_file(&file_path, output).with_context(|| {
76 format!(
77 "error writing generated typeshare types for crate {crate_name} to '{}'",
78 file_path.display()
79 )
80 })?;
81
82 output_files.push((crate_name, file_path));
83 }
84
85 output_files.sort_by_key(|&(crate_name, _)| crate_name);
86
87 lang.write_additional_files(
88 output_folder,
89 output_files
90 .iter()
91 .map(|(crate_name, file_path)| (*crate_name, file_path.as_path())),
92 )
93 .context("failed to write additional files")?;
94
95 Ok(())
96}
97
98pub fn write_single_file<'c>(
100 lang: &impl Language<'c>,
101 file_name: &Path,
102 parsed_data: &ParsedData,
103) -> Result<(), anyhow::Error> {
104 let mut output = Vec::new();
105
106 generate_types(lang, &mut output, parsed_data, FilesMode::Single)
107 .context("error generating typeshare types")?;
108
109 let outfile = Path::new(file_name).to_path_buf();
110 check_write_file(&outfile, output)
111 .context("error writing generated typeshare types to file")?;
112 Ok(())
113}
114
115fn check_write_file(outfile: &PathBuf, output: Vec<u8>) -> anyhow::Result<()> {
117 match fs::read(outfile) {
118 Ok(buf) if buf == output => {
119 eprintln!("Skipping writing to {outfile:?} no changes");
123 return Ok(());
124 }
125 _ => {}
126 }
127
128 if !output.is_empty() {
129 let out_dir = outfile
130 .parent()
131 .context(format!("Could not get parent for {outfile:?}"))?;
132 if !out_dir.exists() {
134 fs::create_dir_all(out_dir).context("failed to create output directory")?;
135 }
136
137 fs::write(outfile, output).context("failed to write output")?;
138 }
139 Ok(())
140}
141
142#[non_exhaustive]
145#[derive(Debug, Clone, Copy, PartialEq)]
146pub enum BorrowedRustItem<'a> {
147 Struct(&'a RustStruct),
149 Enum(&'a RustEnum),
151 Alias(&'a RustTypeAlias),
153 Const(&'a RustConst),
155}
156
157impl BorrowedRustItem<'_> {
158 pub fn name(&self) -> &str {
159 match *self {
160 BorrowedRustItem::Struct(item) => &item.id,
161 BorrowedRustItem::Enum(item) => &item.shared().id,
162 BorrowedRustItem::Alias(item) => &item.id,
163 BorrowedRustItem::Const(item) => &item.id,
164 }
165 .original
166 .as_str()
167 }
168}
169
170fn generate_types<'c>(
173 lang: &impl Language<'c>,
174 out: &mut Vec<u8>,
175 data: &ParsedData,
176 mode: FilesMode<&CrateName>,
177) -> anyhow::Result<()> {
178 lang.begin_file(out, mode)
179 .context("error writing file header")?;
180
181 if let FilesMode::Multi(crate_name) = mode {
182 let all_types = HashMap::new();
183 lang.write_imports(out, crate_name, used_imports(&data, crate_name, &all_types))
184 .context("error writing imports")?;
185 }
186
187 let ParsedData {
188 structs,
189 enums,
190 aliases,
191 consts,
192 ..
193 } = data;
194
195 let mut items = Vec::from_iter(
196 aliases
197 .iter()
198 .map(BorrowedRustItem::Alias)
199 .chain(structs.iter().map(BorrowedRustItem::Struct))
200 .chain(enums.iter().map(BorrowedRustItem::Enum))
201 .chain(consts.iter().map(BorrowedRustItem::Const)),
202 );
203
204 topsort(&mut items);
205
206 for thing in &items {
207 let name = thing.name();
208
209 match thing {
210 BorrowedRustItem::Enum(e) => lang
211 .write_enum(out, e)
212 .with_context(|| format!("error writing enum {name}"))?,
213 BorrowedRustItem::Struct(s) => lang
214 .write_struct(out, s)
215 .with_context(|| format!("error writing struct {name}"))?,
216 BorrowedRustItem::Alias(a) => lang
217 .write_type_alias(out, a)
218 .with_context(|| format!("error writing type alias {name}"))?,
219 BorrowedRustItem::Const(c) => lang
220 .write_const(out, c)
221 .with_context(|| format!("error writing const {name}"))?,
222 }
223 }
224
225 lang.end_file(out, mode)
226 .context("error writing file trailer")
227}
228
229fn used_imports<'a, 'b: 'a>(
232 data: &'b ParsedData,
233 crate_name: &CrateName,
234 all_types: &'a HashMap<CrateName, HashSet<TypeName>>,
235) -> BTreeMap<&'a CrateName, BTreeSet<&'a TypeName>> {
236 let mut used_imports: BTreeMap<&'a CrateName, BTreeSet<&'a TypeName>> = BTreeMap::new();
237
238 let fallback = |referenced_import: &'a ImportedType,
241 used: &mut BTreeMap<&'a CrateName, BTreeSet<&'a TypeName>>| {
242 if let Some((crate_name, ty)) = all_types
244 .iter()
245 .flat_map(|(k, v)| {
246 v.iter()
247 .find(|&t| *t == referenced_import.type_name && k != crate_name)
248 .map(|t| (k, t))
249 })
250 .next()
251 {
252 println!("Warning: Using {crate_name} as module for {ty} which is not in referenced crate {}", referenced_import.base_crate);
253 used.entry(crate_name).or_default().insert(ty);
254 } else {
255 }
257 };
258
259 for referenced_import in data
260 .import_types
261 .iter()
262 .filter(|imp| imp.base_crate != *crate_name)
265 {
266 if let Some(type_names) = all_types.get(&referenced_import.base_crate) {
268 if referenced_import.type_name == "*" {
269 used_imports
271 .entry(&referenced_import.base_crate)
272 .and_modify(|names| names.extend(type_names.iter()));
273 } else if let Some(ty_name) = type_names.get(&referenced_import.type_name) {
274 used_imports
276 .entry(&referenced_import.base_crate)
277 .or_default()
278 .insert(ty_name);
279 } else {
280 fallback(referenced_import, &mut used_imports);
281 }
282 } else {
283 fallback(referenced_import, &mut used_imports);
285 }
286 }
287 used_imports
288}