1#![allow(unused_doc_comments)]
2
3mod to_typescript;
4mod typescript;
5pub mod utils;
6pub(crate) mod casing;
7pub(crate) mod holochain_imports;
8mod parser;
9
10
11use std::fs::File;
12use std::io::{BufRead, BufReader, Write};
13use std::path::{Path, PathBuf};
14use walkdir::WalkDir;
15use crate::parser::*;
16
17
18const MAGIC_FIRST_LINE: &str = "/* This file is generated by zits. Do not edit manually */";
19
20
21pub struct GenConfig {
22 can_debug: bool,
23 can_hc_imports: bool,
25 uses_typeinterface: bool,
26 zome_name: String,
27}
28
29
30pub fn generate_typescript_bindings(
32 input: Vec<PathBuf>,
33 external_imports: Vec<String>,
34 output: PathBuf,
35 can_debug: bool,
36 can_hc_imports: bool,
37 can_proxy: bool,
38 can_fns: bool,
39 external_fns: Vec<String>,
40 maybe_default_zome_name: Option<String>,
41) {
42 let uses_typeinterface = output
43 .as_os_str()
44 .to_str()
45 .map(|x| x.ends_with(".d.ts"))
46 .unwrap_or(true);
47
48
49 let file_name = output.file_stem().unwrap().to_str().unwrap();
50 let zome_name: &str = file_name.split(".").collect::<Vec<&str>>()[0];
51 let default_zome_name: String = if let Some(dzn) = maybe_default_zome_name {
52 dzn
53 } else {
54 zome_name.to_string()
55 };
56
57 let mut state: ParseState = ParseState::new(
58 GenConfig {
59 can_debug,
60 can_hc_imports,
62 uses_typeinterface,
63 zome_name: zome_name.to_string(),
64 });
65
66 state.set_external_import_header(external_imports);
67
68 if !can_debug {
69 state.write_type_defs_header();
70 state.write_zome_fn_names_header(&zome_name);
71 state.write_zome_integrity_header(&zome_name, &default_zome_name);
72 }
73
74
75 for input_path in input {
77 if !input_path.exists() {
78 if can_debug {
79 println!("Path `{:#?}` does not exist", input_path);
80 }
81
82 state.unprocessed_files.push(input_path);
83 continue;
84 }
85
86 if input_path.is_dir() {
87 for entry in WalkDir::new(input_path.clone()).sort_by_file_name() {
88 match entry {
89 Ok(dir_entry) => {
90 let path = dir_entry.into_path();
91
92 if !path.is_dir() {
94 let extension = path.extension();
96 if extension.is_some() && extension.unwrap().eq_ignore_ascii_case("rs")
97 {
98 state.parse_rust_file(path);
99 } else if can_debug {
100 println!("Encountered non-rust file `{:#?}`", path);
101 }
102 } else if can_debug {
103 println!("Encountered directory `{:#?}`", path);
104 }
105 }
106 Err(_) => {
107 println!(
108 "An error occurred whilst walking directory `{:#?}`...",
109 input_path.clone()
110 );
111 continue;
112 }
113 }
114 }
115 } else {
116 state.parse_rust_file(input_path);
117 }
118 }
119
120
121
122 if can_proxy {
123 let copy = state.zome_proxy_output.clone();
125 state.zome_proxy_output.clear();
126 state.write_zome_proxy_header(&zome_name, &default_zome_name);
127 state.zome_proxy_output.push_str(©);
128 state.zome_proxy_output.push_str(&format!("}}\n"));
130 state.write_type_defs_import(&zome_name);
132 state.write_zome_fn_names_footer(external_fns, &zome_name, &default_zome_name);
134 }
135
136 state.write_zome_integrity_footer(&zome_name, &default_zome_name);
137
138 if can_debug {
140 println!("\n");
141 println!("======================================");
142 println!("Debug mode try run output");
143 println!("======================================");
144 println!("TYPE DEFS FILE for \"{}\"", zome_name);
146 println!("======================================");
147 println!("{}", state.type_defs_output);
148 println!("======================================");
149 println!("INTEGRITY TYPES FILE for \"{}\"", zome_name);
151 println!("======================================");
152 println!("{}", state.zome_integrity_output);
153 println!("======================================");
154 if can_proxy {
156 println!("ZomeProxy FILE for \"{}\"", zome_name);
157 println!("======================================");
158 println!("{}", state.zome_proxy_output);
159 println!("======================================");
160 }
161 if can_fns {
162 println!("Function Names for \"{}\"", zome_name);
163 println!("======================================");
164 println!("{}", state.zome_fn_names_output);
165 println!("======================================");
166 }
167 } else {
168 println!("======================================");
169 println!("Bindings generated for \"{}\"", zome_name);
170 println!("======================================");
171
172 let count_const = state.converted_items["const"].len();
173 let count_type = state.converted_items["type"].len();
174 let count_struct = state.converted_items["struct"].len();
175 let count_enum = state.converted_items["enum"].len();
176 let count_fn = state.converted_items["fn"].len();
177
178 println!("Total Items found: {}", count_const + count_type + count_struct + count_enum + count_fn);
179 if count_const > 0 {println!(" - const: {}", count_const)}
180 if count_type > 0 {println!(" - type: {}", count_type)}
181 if count_struct > 0 {println!(" - struct: {}", count_struct)}
182 if count_enum > 0 {println!(" - enum: {}", count_enum)}
183 if count_fn > 0 {println!(" - fn: {}", count_fn)}
184
185 let original_file_path = Path::new(&output);
187 if original_file_path.exists() {
188 if !original_file_path.is_file() {
189 panic!("Specified output path is a directory but must be a file.")
190 }
191 let original_file = File::open(original_file_path).expect("Couldn't open output file");
192 let mut buffer = BufReader::new(original_file);
193
194 let mut first_line = String::new();
195
196 buffer
197 .read_line(&mut first_line)
198 .expect("Unable to read line");
199
200 if first_line.trim() != MAGIC_FIRST_LINE {
201 panic!("Aborting: specified output file exists but doesn't seem to be a zits output file: {}", first_line)
202 }
203 }
204
205 if count_const + count_type + count_struct + count_enum > 0 {
206 let mut types_output: PathBuf = output.clone();
207 types_output.set_file_name(format!("{}.types.ts", zome_name));
208 let mut types_file: File = File::create(&types_output).expect("Unable to write to file");
209 match types_file.write_all(state.type_defs_output.as_bytes()) {
210 Ok(_) => println!("Successfully generated types: {:#?}", types_output),
211 Err(_) => println!("Failed to generate types, an error occurred."),
212 }
213 } else {
214 println!("Types file not generated as no types have been found.");
215 }
216
217 let mut integrity_output: PathBuf = output.clone();
219 integrity_output.set_file_name(format!("{}.integrity.ts", zome_name));
220 let mut proxy_file: File = File::create(&integrity_output).expect("Unable to write to file");
222 match proxy_file.write_all(state.zome_integrity_output.as_bytes()) {
223 Ok(_) => println!("Successfully generated Integrity: {:#?}", integrity_output),
224 Err(_) => println!("Failed to generate Integrity, an error occurred."),
225 }
226
227 if can_proxy {
229 let mut proxy_output: PathBuf = output.clone();
230 proxy_output.set_file_name(format!("{}.proxy.ts", zome_name));
231 let mut proxy_file: File = File::create(&proxy_output).expect("Unable to write to file");
233 match proxy_file.write_all(state.zome_proxy_output.as_bytes()) {
234 Ok(_) => println!("Successfully generated ZomeProxy: {:#?}", proxy_output),
235 Err(_) => println!("Failed to generate ZomeProxy, an error occurred."),
236 }
237 }
238
239 if can_fns {
241 if count_fn > 0 {
242 let mut fn_output: PathBuf = output.clone();
243 fn_output.set_file_name(format!("{}.fn.ts", zome_name));
244 let mut fn_file: File = File::create(&fn_output).expect("Unable to write to file");
246 match fn_file.write_all(state.zome_fn_names_output.as_bytes()) {
247 Ok(_) => println!("Successfully generated FnNames: {:#?}", fn_output),
248 Err(_) => println!("Failed to generate FnNames, an error occurred."),
249 }
250 } else {
251 println!("FnNames file not generated as no functions have been found");
252 }
253 }
254 }
255
256 if state.unprocessed_files.len() > 0 {
257 println!("[zits][info] Could not parse the following files:");
258 for unprocessed_file in state.unprocessed_files {
259 println!("• {:#?}", unprocessed_file);
260 }
261 }
262 println!("======================================");
263}