1mod filter;
2mod formatter;
3pub mod generate;
4mod indexed;
5mod item;
6mod node;
7mod serde;
8mod serde_generate;
9
10use std::{
11 collections::{BTreeMap, HashMap},
12 fs::{self, File},
13 io::Read,
14 path::PathBuf,
15 process::Command,
16};
17
18use anyhow::{anyhow, bail, Context, Result};
19use guppy::{graph::PackageGraph, MetadataCommand};
20use log::debug;
21use rustdoc_types::Crate;
22
23use crate::args::CodegenArgs;
24use filter::Filter;
25use formatter::Formatter;
26use node::ItemNode;
27use serde_generate::format::ContainerFormat;
28
29pub type Registry = BTreeMap<String, ContainerFormat>;
30
31pub fn codegen(args: &CodegenArgs) -> Result<()> {
32 let mut cmd = MetadataCommand::new();
33 let package_graph = PackageGraph::from_command(&mut cmd)?;
34
35 let manifest_paths: BTreeMap<&str, &str> = package_graph
36 .packages()
37 .map(|package| (package.name(), package.manifest_path().as_str()))
38 .collect();
39
40 let Ok(lib) = package_graph.workspace().member_by_path(&args.lib) else {
41 bail!("Could not find workspace package with path {}", args.lib)
42 };
43
44 let lib_name = lib.name();
45
46 let registry = run(lib_name, |name| load_crate(name, &manifest_paths))?;
47
48 let registry: serde_reflection::Registry =
50 serde_json::from_slice(&serde_json::to_vec(®istry)?)?;
51
52 fs::create_dir_all(&args.output)?;
53
54 generate::java(®istry, &args.java_package, args.output.join("java"))
55 .context("Generating types for Java")?;
56
57 generate::swift(®istry, &args.swift_package, args.output.join("swift"))
58 .context("Generating tyeps for Swift")?;
59
60 generate::typescript(
61 ®istry,
62 &args.typescript_package,
63 &lib.version().to_string(),
64 args.output.join("typescript"),
65 )
66 .context("Generating types for TypeScript")?;
67 Ok(())
68}
69
70fn run<F>(crate_name: &str, load: F) -> Result<Registry>
71where
72 F: Fn(&str) -> Result<Crate>,
73{
74 let mut previous: HashMap<String, Crate> = HashMap::new();
75
76 let shared_lib = load(crate_name)?;
77
78 let mut filter = Filter::default();
79 filter.process(crate_name, &shared_lib)?;
80
81 previous.insert(crate_name.to_string(), shared_lib);
82
83 let mut next: Vec<String> = filter.get_crates();
84
85 while let Some(crate_name) = next.pop() {
86 if previous.contains_key(&crate_name) {
87 continue;
88 }
89 let crate_ = load(&crate_name)?;
90
91 filter.process(&crate_name, &crate_)?;
92
93 next = filter.get_crates();
94 previous.insert(crate_name, crate_);
95 }
96
97 Ok(format(filter.edge))
98}
99
100fn format(edges: Vec<(ItemNode, ItemNode)>) -> Registry {
101 let mut formatter = Formatter::default();
102 formatter.edge = edges;
103 formatter.run();
104 debug!("{}", formatter.scc_times_summary());
105
106 formatter.container.into_iter().collect()
107}
108
109fn load_crate(name: &str, manifest_paths: &BTreeMap<&str, &str>) -> Result<Crate> {
110 let json_path = if let "core" | "alloc" | "std" = name {
114 rustdoc_json_path()?.join(format!("{name}.json"))
115 } else {
116 let manifest_path = manifest_paths
117 .get(name)
118 .ok_or_else(|| anyhow!("unknown crate {}", name))?;
119 rustdoc_json::Builder::default()
120 .toolchain("nightly")
121 .document_private_items(true)
122 .manifest_path(manifest_path)
123 .build()?
124 };
125 debug!("from {}", json_path.to_string_lossy());
126
127 let buf = &mut Vec::new();
128 File::open(json_path)?.read_to_end(buf)?;
129 let crate_ = serde_json::from_slice(buf)?;
130
131 Ok(crate_)
132}
133
134fn rustdoc_json_path() -> Result<PathBuf> {
135 let output = Command::new("rustup")
136 .arg("which")
137 .args(["--toolchain", "nightly"])
138 .arg("rustc")
139 .output()?;
140 let rustc_path = std::str::from_utf8(&output.stdout)?.trim();
141 let json_path = PathBuf::from(rustc_path)
142 .parent()
143 .ok_or_else(|| anyhow!("could not get parent of {}", rustc_path))?
144 .parent()
145 .ok_or_else(|| anyhow!("could not get grandparent of {}", rustc_path))?
146 .join("share/doc/rust/json");
147
148 Ok(json_path)
149}
150
151pub fn collect<'a, N, T>(input: T) -> impl Iterator<Item = Vec<(&'a N,)>>
152where
153 N: 'a + Clone,
154 T: Iterator<Item = (&'a N,)>,
155{
156 std::iter::once(input.collect::<Vec<_>>())
157}
158
159#[cfg(test)]
160mod tests;