1use std::path::{Path, PathBuf};
10use std::process::ExitCode;
11
12use bynk_emit::project::{self, CompileOptions, read_project_paths};
13use bynk_fmt::{FormatOptions, format_source};
14
15pub fn project_options(input: &Path) -> CompileOptions {
23 if input.join("bynk.toml").exists() || input.join("src").is_dir() {
24 CompileOptions::split(input.to_path_buf(), read_project_paths(input))
25 } else {
26 CompileOptions::single(input.to_path_buf())
27 }
28}
29
30pub fn print_project_failure(failure: &project::ProjectFailure) {
39 let texts: std::collections::HashMap<&Path, &str> = failure
40 .snapshots
41 .iter()
42 .map(|(p, t)| (p.as_path(), t.as_str()))
43 .collect();
44 for ae in &failure.errors {
45 match ae
46 .source_path
47 .as_deref()
48 .and_then(|p| texts.get(p).map(|t| (p, *t)))
49 {
50 Some((path, text)) => {
51 let label = path.to_string_lossy().replace('\\', "/");
52 bynk_render::print_errors(std::slice::from_ref(&ae.error), text, &label);
53 }
54 None => {
55 eprintln!("[{}] {}", ae.error.category, ae.error.message);
56 for note in &ae.error.notes {
57 eprintln!(" note: {note}");
58 }
59 }
60 }
61 }
62}
63
64pub fn print_project_warnings(warnings: &[project::AttributedError]) {
69 for w in warnings {
70 let where_ = w
71 .source_path
72 .as_deref()
73 .map(|p| format!("{}: ", p.to_string_lossy().replace('\\', "/")))
74 .unwrap_or_default();
75 eprintln!("{where_}warning[{}]: {}", w.error.category, w.error.message);
76 for note in &w.error.notes {
77 eprintln!(" note: {note}");
78 }
79 }
80}
81
82pub fn print_project_failure_short(failure: &project::ProjectFailure) {
86 for line in project_failure_short_lines(failure) {
87 eprintln!("{line}");
88 }
89}
90
91pub fn project_failure_short_lines(failure: &project::ProjectFailure) -> Vec<String> {
100 let texts: std::collections::HashMap<&Path, &str> = failure
101 .snapshots
102 .iter()
103 .map(|(p, t)| (p.as_path(), t.as_str()))
104 .collect();
105 failure
106 .errors
107 .iter()
108 .map(|ae| {
109 match ae
110 .source_path
111 .as_deref()
112 .and_then(|p| texts.get(p).map(|t| (p, *t)))
113 {
114 Some((path, text)) => {
115 let label = path.to_string_lossy().replace('\\', "/");
116 bynk_render::short_line(&label, text, &ae.error)
117 }
118 None => format!(
119 "{}[{}]: {}",
120 bynk_render::severity_word(&ae.error),
121 ae.error.category,
122 ae.error.message
123 ),
124 }
125 })
126 .collect()
127}
128
129pub fn run_fmt(prog: &str, inputs: &[PathBuf], check: bool) -> ExitCode {
134 let opts = FormatOptions::default();
135 if inputs.is_empty() {
136 eprintln!("{prog} fmt: no input files (pass file paths or `-` for stdin)");
137 return ExitCode::FAILURE;
138 }
139 let mut had_diff = false;
140 let mut had_error = false;
141 for input in inputs {
142 if input.as_os_str() == "-" {
143 use std::io::Read;
144 let mut source = String::new();
145 if let Err(e) = std::io::stdin().read_to_string(&mut source) {
146 eprintln!("{prog} fmt: read from stdin: {e}");
147 return ExitCode::FAILURE;
148 }
149 match format_source(&source, &opts) {
150 Ok(formatted) => print!("{formatted}"),
151 Err(e) => {
152 bynk_render::print_errors(&e.errors, &source, "<stdin>");
153 return ExitCode::FAILURE;
154 }
155 }
156 continue;
157 }
158 let source = match std::fs::read_to_string(input) {
159 Ok(s) => s,
160 Err(e) => {
161 eprintln!("{prog} fmt: read `{}`: {e}", input.display());
162 had_error = true;
163 continue;
164 }
165 };
166 let filename = input.display().to_string();
167 match format_source(&source, &opts) {
168 Ok(formatted) => {
169 if check {
170 if formatted != source {
171 eprintln!(
172 "{prog} fmt: {} is not canonically formatted",
173 input.display()
174 );
175 had_diff = true;
176 }
177 } else if formatted != source
178 && let Err(e) = std::fs::write(input, formatted)
179 {
180 eprintln!("{prog} fmt: write `{}`: {e}", input.display());
181 had_error = true;
182 }
183 }
184 Err(e) => {
185 bynk_render::print_errors(&e.errors, &source, &filename);
186 had_error = true;
187 }
188 }
189 }
190 if had_error || (check && had_diff) {
191 ExitCode::FAILURE
192 } else {
193 ExitCode::SUCCESS
194 }
195}
196
197pub fn run_check(prog: &str, input: &Path, short: bool) -> ExitCode {
202 if input.is_dir() {
203 match project::compile_project(&project_options(input)) {
204 Ok(out) => {
205 print_project_warnings(&out.warnings);
206 ExitCode::SUCCESS
207 }
208 Err(failure) => {
209 if short {
210 print_project_failure_short(&failure);
211 } else {
212 print_project_failure(&failure);
213 }
214 ExitCode::FAILURE
215 }
216 }
217 } else {
218 let source = match std::fs::read_to_string(input) {
219 Ok(s) => s,
220 Err(e) => {
221 eprintln!("{prog}: could not read `{}`: {e}", input.display());
222 return ExitCode::FAILURE;
223 }
224 };
225 let filename = input.display().to_string();
226 match bynk_emit::compile_with_warnings(&source, &filename) {
227 Ok(compiled) => {
228 if !compiled.warnings.is_empty() {
229 if short {
230 bynk_render::print_errors_short(&compiled.warnings, &source, &filename);
231 } else {
232 bynk_render::print_errors(&compiled.warnings, &source, &filename);
233 }
234 }
235 ExitCode::SUCCESS
236 }
237 Err(errors) => {
238 if short {
239 bynk_render::print_errors_short(&errors, &source, &filename);
240 } else {
241 bynk_render::print_errors(&errors, &source, &filename);
242 }
243 ExitCode::FAILURE
244 }
245 }
246 }
247}