ts_rs_json_value/
export.rs1use std::{
2 any::TypeId,
3 collections::BTreeMap,
4 fmt::Write,
5 path::{Component, Path, PathBuf},
6};
7
8use thiserror::Error;
9use ExportError::*;
10
11use crate::TS;
12
13const NOTE: &str = "// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.\n";
14
15#[derive(Error, Debug)]
17pub enum ExportError {
18 #[error("this type cannot be exported")]
19 CannotBeExported,
20 #[cfg(feature = "format")]
21 #[error("an error occurred while formatting the generated typescript output")]
22 Formatting(String),
23 #[error("an error occurred while performing IO")]
24 Io(#[from] std::io::Error),
25 #[error("the environment variable CARGO_MANIFEST_DIR is not set")]
26 ManifestDirNotSet,
27}
28
29pub(crate) fn export_type<T: TS + ?Sized + 'static>() -> Result<(), ExportError> {
31 let path = output_path::<T>()?;
32 export_type_to::<T, _>(&path)
33}
34
35pub(crate) fn export_type_to<T: TS + ?Sized + 'static, P: AsRef<Path>>(
37 path: P,
38) -> Result<(), ExportError> {
39 #[allow(unused_mut)]
40 let mut buffer = export_type_to_string::<T>()?;
41
42 #[cfg(feature = "format")]
44 {
45 use dprint_plugin_typescript::{configuration::ConfigurationBuilder, format_text};
46
47 let fmt_cfg = ConfigurationBuilder::new().deno().build();
48 if let Some(formatted) =
49 format_text(path.as_ref(), &buffer, &fmt_cfg).map_err(|e| Formatting(e.to_string()))?
50 {
51 buffer = formatted;
52 }
53 }
54
55 if let Some(parent) = path.as_ref().parent() {
56 std::fs::create_dir_all(parent)?;
57 }
58 std::fs::write(path.as_ref(), buffer)?;
59 Ok(())
60}
61
62pub(crate) fn export_type_to_string<T: TS + ?Sized + 'static>() -> Result<String, ExportError> {
64 let mut buffer = String::with_capacity(1024);
65 buffer.push_str(NOTE);
66 generate_imports::<T>(&mut buffer)?;
67 generate_decl::<T>(&mut buffer);
68 Ok(buffer)
69}
70
71fn output_path<T: TS + ?Sized>() -> Result<PathBuf, ExportError> {
73 let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").map_err(|_| ManifestDirNotSet)?;
74 let manifest_dir = Path::new(&manifest_dir);
75 let path = PathBuf::from(T::EXPORT_TO.ok_or(CannotBeExported)?);
76 Ok(manifest_dir.join(path))
77}
78
79fn generate_decl<T: TS + ?Sized>(out: &mut String) {
81 out.push_str("export ");
82 out.push_str(&T::decl());
83}
84
85fn generate_imports<T: TS + ?Sized + 'static>(out: &mut String) -> Result<(), ExportError> {
87 let path = Path::new(T::EXPORT_TO.ok_or(ExportError::CannotBeExported)?);
88
89 let deps = T::dependencies();
90 let deduplicated_deps = deps
91 .iter()
92 .filter(|dep| dep.type_id != TypeId::of::<T>())
93 .map(|dep| (&dep.ts_name, dep))
94 .collect::<BTreeMap<_, _>>();
95
96 for (_, dep) in deduplicated_deps {
97 let rel_path = import_path(path, Path::new(dep.exported_to));
98 writeln!(
99 out,
100 "import type {{ {} }} from {:?};",
101 &dep.ts_name, rel_path
102 )
103 .unwrap();
104 }
105 writeln!(out).unwrap();
106 Ok(())
107}
108
109fn import_path(from: &Path, import: &Path) -> String {
111 let rel_path =
112 diff_paths(import, from.parent().unwrap()).expect("failed to calculate import path");
113 match rel_path.components().next() {
114 Some(Component::Normal(_)) => format!("./{}", rel_path.to_string_lossy()),
115 _ => rel_path.to_string_lossy().into(),
116 }
117 .trim_end_matches(".ts")
118 .to_owned()
119}
120
121fn diff_paths<P, B>(path: P, base: B) -> Option<PathBuf>
134where
135 P: AsRef<Path>,
136 B: AsRef<Path>,
137{
138 let path = path.as_ref();
139 let base = base.as_ref();
140
141 if path.is_absolute() != base.is_absolute() {
142 if path.is_absolute() {
143 Some(PathBuf::from(path))
144 } else {
145 None
146 }
147 } else {
148 let mut ita = path.components();
149 let mut itb = base.components();
150 let mut comps: Vec<Component> = vec![];
151 loop {
152 match (ita.next(), itb.next()) {
153 (None, None) => break,
154 (Some(a), None) => {
155 comps.push(a);
156 comps.extend(ita.by_ref());
157 break;
158 }
159 (None, _) => comps.push(Component::ParentDir),
160 (Some(a), Some(b)) if comps.is_empty() && a == b => (),
161 (Some(a), Some(Component::CurDir)) => comps.push(a),
162 (Some(_), Some(Component::ParentDir)) => return None,
163 (Some(a), Some(_)) => {
164 comps.push(Component::ParentDir);
165 for _ in itb {
166 comps.push(Component::ParentDir);
167 }
168 comps.push(a);
169 comps.extend(ita.by_ref());
170 break;
171 }
172 }
173 }
174 Some(comps.iter().map(|c| c.as_os_str()).collect())
175 }
176}