veryl 0.20.1

A modern hardware description language
use crate::OptTranslate;
use log::{info, warn};
use miette::{IntoDiagnostic, Result, WrapErr};
use std::fs;
use std::path::{Path, PathBuf};
use veryl_metadata::{Metadata, NewlineStyle};

pub struct CmdTranslate {
    opt: OptTranslate,
}

impl CmdTranslate {
    pub fn new(opt: OptTranslate) -> Self {
        Self { opt }
    }

    pub fn exec(&self) -> Result<bool> {
        if self.opt.files.is_empty() {
            return Err(miette::miette!(
                "no input files; pass one or more `.sv` paths"
            ));
        }

        let newline_style = match Metadata::search_from_current() {
            Ok(p) => Metadata::load(p)
                .map(|m| m.format.newline_style)
                .unwrap_or(NewlineStyle::Auto),
            Err(_) => NewlineStyle::Auto,
        };

        let mut all_pass = true;
        for input in &self.opt.files {
            all_pass &= self.translate_one(input, newline_style)?;
        }
        Ok(all_pass)
    }

    fn translate_one(&self, input: &Path, newline_style: NewlineStyle) -> Result<bool> {
        info!("Translating ({})", input.to_string_lossy());

        let src = fs::read_to_string(input)
            .into_diagnostic()
            .wrap_err("failed to read input")?;

        let out = veryl_translator::translate_str(&src, input, !self.opt.no_format, newline_style)
            .map_err(|e| miette::miette!("{e}"))?;

        let unsupported_count = out.unsupported.len();
        if unsupported_count > 0 {
            // Render each unsupported construct as a graphical miette warning
            // (source span underlined, reason shown as help), the same way
            // `veryl check` presents analyzer diagnostics.
            for c in out.unsupported {
                eprintln!("{:?}", miette::Report::new(c));
            }
            warn!(
                "{}: {unsupported_count} unsupported construct(s)",
                input.to_string_lossy(),
            );
        }

        if self.opt.stdout {
            print!("{}", out.veryl);
        } else {
            let dest = sibling_veryl_path(input)?;
            fs::write(&dest, out.veryl.as_bytes())
                .into_diagnostic()
                .wrap_err_with(|| format!("failed to write {}", dest.to_string_lossy()))?;
            info!("Wrote ({})", dest.to_string_lossy());
        }

        if self.opt.strict && unsupported_count > 0 {
            Ok(false)
        } else {
            Ok(true)
        }
    }
}

fn sibling_veryl_path(input: &Path) -> Result<PathBuf> {
    let mut dest = input.to_path_buf();
    if !dest.set_extension("veryl") {
        return Err(miette::miette!(
            "cannot derive output path for {}",
            input.to_string_lossy()
        ));
    }
    Ok(dest)
}