use std::path::{Path, PathBuf};
use std::process::ExitCode;
use bynk_emit::project::{self, CompileOptions, read_project_paths};
use bynk_fmt::{FormatOptions, format_source};
pub fn project_options(input: &Path) -> CompileOptions {
if input.join("bynk.toml").exists() || input.join("src").is_dir() {
CompileOptions::split(input.to_path_buf(), read_project_paths(input))
} else {
CompileOptions::single(input.to_path_buf())
}
}
pub fn print_project_failure(failure: &project::ProjectFailure) {
let texts: std::collections::HashMap<&Path, &str> = failure
.snapshots
.iter()
.map(|(p, t)| (p.as_path(), t.as_str()))
.collect();
for ae in &failure.errors {
match ae
.source_path
.as_deref()
.and_then(|p| texts.get(p).map(|t| (p, *t)))
{
Some((path, text)) => {
let label = path.to_string_lossy().replace('\\', "/");
bynk_render::print_errors(std::slice::from_ref(&ae.error), text, &label);
}
None => {
eprintln!("[{}] {}", ae.error.category, ae.error.message);
for note in &ae.error.notes {
eprintln!(" note: {note}");
}
}
}
}
}
pub fn print_project_warnings(warnings: &[project::AttributedError]) {
for w in warnings {
let where_ = w
.source_path
.as_deref()
.map(|p| format!("{}: ", p.to_string_lossy().replace('\\', "/")))
.unwrap_or_default();
eprintln!("{where_}warning[{}]: {}", w.error.category, w.error.message);
for note in &w.error.notes {
eprintln!(" note: {note}");
}
}
}
pub fn print_project_failure_short(failure: &project::ProjectFailure) {
for line in project_failure_short_lines(failure) {
eprintln!("{line}");
}
}
pub fn project_failure_short_lines(failure: &project::ProjectFailure) -> Vec<String> {
let texts: std::collections::HashMap<&Path, &str> = failure
.snapshots
.iter()
.map(|(p, t)| (p.as_path(), t.as_str()))
.collect();
failure
.errors
.iter()
.map(|ae| {
match ae
.source_path
.as_deref()
.and_then(|p| texts.get(p).map(|t| (p, *t)))
{
Some((path, text)) => {
let label = path.to_string_lossy().replace('\\', "/");
bynk_render::short_line(&label, text, &ae.error)
}
None => format!(
"{}[{}]: {}",
bynk_render::severity_word(&ae.error),
ae.error.category,
ae.error.message
),
}
})
.collect()
}
pub fn run_fmt(prog: &str, inputs: &[PathBuf], check: bool) -> ExitCode {
let opts = FormatOptions::default();
if inputs.is_empty() {
eprintln!("{prog} fmt: no input files (pass file paths or `-` for stdin)");
return ExitCode::FAILURE;
}
let mut had_diff = false;
let mut had_error = false;
for input in inputs {
if input.as_os_str() == "-" {
use std::io::Read;
let mut source = String::new();
if let Err(e) = std::io::stdin().read_to_string(&mut source) {
eprintln!("{prog} fmt: read from stdin: {e}");
return ExitCode::FAILURE;
}
match format_source(&source, &opts) {
Ok(formatted) => print!("{formatted}"),
Err(e) => {
bynk_render::print_errors(&e.errors, &source, "<stdin>");
return ExitCode::FAILURE;
}
}
continue;
}
let source = match std::fs::read_to_string(input) {
Ok(s) => s,
Err(e) => {
eprintln!("{prog} fmt: read `{}`: {e}", input.display());
had_error = true;
continue;
}
};
let filename = input.display().to_string();
match format_source(&source, &opts) {
Ok(formatted) => {
if check {
if formatted != source {
eprintln!(
"{prog} fmt: {} is not canonically formatted",
input.display()
);
had_diff = true;
}
} else if formatted != source
&& let Err(e) = std::fs::write(input, formatted)
{
eprintln!("{prog} fmt: write `{}`: {e}", input.display());
had_error = true;
}
}
Err(e) => {
bynk_render::print_errors(&e.errors, &source, &filename);
had_error = true;
}
}
}
if had_error || (check && had_diff) {
ExitCode::FAILURE
} else {
ExitCode::SUCCESS
}
}
pub fn run_check(prog: &str, input: &Path, short: bool) -> ExitCode {
if input.is_dir() {
match project::compile_project(&project_options(input)) {
Ok(out) => {
print_project_warnings(&out.warnings);
ExitCode::SUCCESS
}
Err(failure) => {
if short {
print_project_failure_short(&failure);
} else {
print_project_failure(&failure);
}
ExitCode::FAILURE
}
}
} else {
let source = match std::fs::read_to_string(input) {
Ok(s) => s,
Err(e) => {
eprintln!("{prog}: could not read `{}`: {e}", input.display());
return ExitCode::FAILURE;
}
};
let filename = input.display().to_string();
match bynk_emit::compile_with_warnings(&source, &filename) {
Ok(compiled) => {
if !compiled.warnings.is_empty() {
if short {
bynk_render::print_errors_short(&compiled.warnings, &source, &filename);
} else {
bynk_render::print_errors(&compiled.warnings, &source, &filename);
}
}
ExitCode::SUCCESS
}
Err(errors) => {
if short {
bynk_render::print_errors_short(&errors, &source, &filename);
} else {
bynk_render::print_errors(&errors, &source, &filename);
}
ExitCode::FAILURE
}
}
}
}