use std::any::TypeId;
use std::collections::{HashMap, HashSet};
use std::path::{Component, Path, PathBuf};
use crate::TS;
use std::fmt::Write;
#[macro_export]
macro_rules! export {
($($($p:path),+ => $l:literal),* $(,)?) => {
#[cfg(test)]
#[test]
fn export_typescript() {
use std::fmt::Write;
use std::collections::{HashMap as __HashMap, HashSet as __HashSet};
let manifest_var = std::env::var("CARGO_MANIFEST_DIR").unwrap();
let manifest_dir = std::path::Path::new(&manifest_var);
let mut files = __HashMap::new();
$(
let path = manifest_dir.join($l);
$({
if let Some(_) = files.insert(std::any::TypeId::of::<$p>(), path.clone()) {
panic!(
"{} cannot be exported to multiple files using `export!`",
stringify!($p),
);
}
})*
)*
let mut buffer = String::with_capacity(8192);
$({
let mut imports = __HashMap::<String, __HashSet<String>>::new();
buffer.clear();
let out = manifest_dir.join($l);
std::fs::create_dir_all(out.parent().unwrap())
.expect("could not create directory");
$( ts_rs::export::imports::<$p>(&files, &mut imports, &out); )*
ts_rs::export::write_imports(imports, &mut buffer);
writeln!(&mut buffer).unwrap();
$( writeln!(&mut buffer, "{}\n", <$p as ts_rs::TS>::decl()).unwrap(); )*
std::fs::write(&out, buffer.trim())
.expect("could not write file");
})*
}
};
}
pub fn write_imports(imports: HashMap<String, HashSet<String>>, out: &mut impl Write) {
for (path, types) in imports {
writeln!(
out,
"import {{{}}} from {:?};",
types.into_iter().collect::<Vec<_>>().join(", "),
path
)
.unwrap();
}
}
pub fn imports<T: TS>(
exported_files: &HashMap<TypeId, PathBuf>,
imports: &mut HashMap<String, HashSet<String>>,
out_path: &Path,
) {
T::dependencies()
.into_iter()
.flat_map(|(id, name)| {
let path = exported_files.get(&id)?;
if path == out_path {
None
} else {
Some((import_path(out_path, path), name))
}
})
.for_each(|(path, name)| {
imports
.entry(path)
.or_insert_with(HashSet::<_>::new)
.insert(name);
});
}
fn import_path(from: &Path, import: &Path) -> String {
let rel_path =
diff_paths(import, from.parent().unwrap()).expect("failed to calculate import path");
match rel_path.components().next() {
Some(Component::Normal(_)) => format!("./{}", rel_path.to_string_lossy()),
_ => rel_path.to_string_lossy().into(),
}
.trim_end_matches(".ts")
.to_owned()
}
fn diff_paths<P, B>(path: P, base: B) -> Option<PathBuf>
where
P: AsRef<Path>,
B: AsRef<Path>,
{
let path = path.as_ref();
let base = base.as_ref();
if path.is_absolute() != base.is_absolute() {
if path.is_absolute() {
Some(PathBuf::from(path))
} else {
None
}
} else {
let mut ita = path.components();
let mut itb = base.components();
let mut comps: Vec<Component> = vec![];
loop {
match (ita.next(), itb.next()) {
(None, None) => break,
(Some(a), None) => {
comps.push(a);
comps.extend(ita.by_ref());
break;
}
(None, _) => comps.push(Component::ParentDir),
(Some(a), Some(b)) if comps.is_empty() && a == b => (),
(Some(a), Some(b)) if b == Component::CurDir => comps.push(a),
(Some(_), Some(b)) if b == Component::ParentDir => return None,
(Some(a), Some(_)) => {
comps.push(Component::ParentDir);
for _ in itb {
comps.push(Component::ParentDir);
}
comps.push(a);
comps.extend(ita.by_ref());
break;
}
}
}
Some(comps.iter().map(|c| c.as_os_str()).collect())
}
}