tighterror_build/
coder.rs1use crate::{
2 errors::{
3 kind::coder::{FAILED_TO_READ_OUTPUT_FILE, FAILED_TO_WRITE_OUTPUT_FILE},
4 TbError,
5 },
6 parser,
7 spec::definitions::STDOUT_PATH,
8};
9use log::error;
10use std::{
11 fs::File,
12 io::{self, Read, Write},
13 path::Path,
14};
15
16mod formatter;
17mod frozen_options;
18pub(crate) use frozen_options::*;
19mod generator;
20use generator::ModuleCode;
21pub(crate) mod idents;
22mod options;
23pub use options::*;
24
25const TMP_FILE_PFX: &str = "tighterror.";
26const TMP_FILE_SFX: &str = ".rs";
27const RUST_FILE_EXTENSION: &str = "rs";
28const ALL_MODULES: &str = "*";
29
30pub fn codegen(opts: &CodegenOptions) -> Result<(), TbError> {
52 let spec = parser::parse(opts.spec.as_deref())?;
53 debug_assert!(!spec.modules.is_empty());
54
55 let frozen = FrozenOptions::new(opts, &spec)?;
56 let modules = generator::spec_to_rust(&frozen, &spec)?;
57
58 match frozen.output {
59 p if p.as_os_str() == STDOUT_PATH => {
60 debug_assert_eq!(modules.len(), 1);
61 let code = modules[0].code.as_bytes();
62 if let Err(e) = io::stdout().lock().write_all(code) {
63 error!("failed to write to stdout: {e}");
64 FAILED_TO_WRITE_OUTPUT_FILE.into()
65 } else {
66 Ok(())
67 }
68 }
69 _ if frozen.update => update_modules(&frozen, &modules),
70 _ => write_modules(&frozen, &modules),
71 }
72}
73
74fn write_modules(frozen: &FrozenOptions, modules: &[ModuleCode]) -> Result<(), TbError> {
75 if frozen.separate_files {
76 let dir = frozen.output.as_path();
77 for m in modules {
78 let mut path = dir.join(&m.name);
79 path.set_extension(RUST_FILE_EXTENSION);
80 write_code(&m.code, &path)?;
81 }
82 } else {
83 debug_assert_eq!(modules.len(), 1);
84 write_code(&modules[0].code, frozen.output.as_path())?;
85 }
86
87 Ok(())
88}
89
90fn write_code(code: &str, path: &Path) -> Result<(), TbError> {
91 let file = match File::options()
92 .write(true)
93 .create(true)
94 .truncate(true)
95 .open(path)
96 {
97 Ok(f) => f,
98 Err(e) => {
99 error!("failed to open the output file {:?}: {e}", path);
100 return FAILED_TO_WRITE_OUTPUT_FILE.into();
101 }
102 };
103
104 write_and_format(code, path, file)
105}
106
107fn write_and_format(code: &str, path: &Path, mut file: File) -> Result<(), TbError> {
108 if let Err(e) = file.write_all(code.as_bytes()) {
109 error!("failed to write to the output file {:?}: {e}", path);
110 return FAILED_TO_WRITE_OUTPUT_FILE.into();
111 }
112 file.flush().ok();
113 drop(file);
114 formatter::rustfmt(path).ok();
115 Ok(())
116}
117
118fn read_code(path: &Path) -> Result<String, TbError> {
119 let mut file = match File::options().read(true).open(path) {
120 Ok(f) => f,
121 Err(e) => {
122 error!("failed to open the output file {:?}: {e}", path);
123 return FAILED_TO_READ_OUTPUT_FILE.into();
124 }
125 };
126
127 let mut data = String::with_capacity(4096);
128 file.read_to_string(&mut data).map_err(|e| {
129 error!("failed to read the output file {:?}: {e}", path);
130 TbError::from(FAILED_TO_WRITE_OUTPUT_FILE)
131 })?;
132
133 Ok(data)
134}
135
136fn update_modules(frozen: &FrozenOptions, modules: &[ModuleCode]) -> Result<(), TbError> {
137 if frozen.separate_files {
138 let dir = frozen.output.as_path();
139 for m in modules {
140 let mut path = dir.join(&m.name);
141 path.set_extension(RUST_FILE_EXTENSION);
142 update_module(&m.code, &path)?;
143 }
144 } else {
145 debug_assert_eq!(modules.len(), 1);
146 update_module(&modules[0].code, frozen.output.as_path())?;
147 }
148
149 Ok(())
150}
151
152fn update_module(code: &str, path: &Path) -> Result<(), TbError> {
153 if !path.exists() {
154 return write_code(code, path);
155 }
156
157 let existing_data = read_code(path)?;
158
159 let dir = match path.parent() {
160 Some(p) if !p.as_os_str().is_empty() => p,
161 _ => Path::new("."),
162 };
163
164 let tmp_file = tempfile::Builder::new()
165 .prefix(TMP_FILE_PFX)
166 .suffix(TMP_FILE_SFX)
167 .tempfile_in(dir)
168 .map_err(|e| {
169 error!("failed to create a temporary file [dir={:?}]: {e}", dir);
170 TbError::from(FAILED_TO_WRITE_OUTPUT_FILE)
171 })?;
172
173 let (tmp_file, tmp_path) = tmp_file.keep().map_err(|e| {
174 error!("failed to keep the temporary file: {e}");
175 TbError::from(FAILED_TO_WRITE_OUTPUT_FILE)
176 })?;
177
178 write_and_format(code, &tmp_path, tmp_file)?;
179
180 let new_data = read_code(&tmp_path)?;
181
182 if existing_data != new_data {
183 std::fs::rename(&tmp_path, path).map_err(|e| {
184 error!(
185 "failed to rename updated file {:?} to output file path {:?}: {e}",
186 tmp_path, path
187 );
188 TbError::from(FAILED_TO_WRITE_OUTPUT_FILE)
189 })
190 } else {
191 std::fs::remove_file(&tmp_path).map_err(|e| {
192 error!("failed to unlink temporary file {:?}: {e}", tmp_path);
193 TbError::from(FAILED_TO_WRITE_OUTPUT_FILE)
194 })
195 }
196}