voltaic-cli 0.1.0

The Voltaic compiler: .sdx in, .xrs containing ordinary Rust out.
use crate::build_command::parse_build_args;

use crate::run::usage;

use voltaic_driver::CheckOutcome;

use voltaic_driver::diagnose;

use std::path::PathBuf;

pub(crate) fn check_command(args: &[String]) -> Result<(), String> {
  let parsed = parse_build_args(args)?;
  let shown = parsed.input.display().to_string();
  let source = std::fs::read_to_string(&parsed.input)
    .map_err(|e| format!("error: cannot read {}: {}", shown, e))?;

  let outcome = voltaic_driver::check(&parsed.input, &source)?;
  report(&outcome, parsed.plain)
}

pub(crate) fn report(outcome: &CheckOutcome, plain: bool) -> Result<(), String> {
  if outcome.diagnostics.is_empty() {
    return Ok(());
  }
  let rendered = voltaic_diag::render(&outcome.diagnostics, plain);
  if outcome.failed {
    return Err(rendered);
  }
  eprintln!("{}", rendered);
  Ok(())
}

pub(crate) fn fmt_command(args: &[String]) -> Result<(), String> {
  let parsed = parse_fmt_args(args)?;
  let mut unformatted: Vec<String> = Vec::new();

  for path in &parsed.inputs {
    let shown = path.display().to_string();
    let source =
      std::fs::read_to_string(path).map_err(|e| format!("error: cannot read {}: {}", shown, e))?;

    let program = voltaic_parser::parse_source(&source)
      .map_err(|failure| voltaic_diag::render(&diagnose(path, &source, &failure), parsed.plain))?;

    let formatted = voltaic_fmt::format(&program, &source);
    if formatted == source {
      continue;
    }

    if parsed.check {
      unformatted.push(shown);
    } else {
      std::fs::write(path, &formatted)
        .map_err(|e| format!("error: cannot write {}: {}", shown, e))?;
    }
  }

  if unformatted.is_empty() {
    return Ok(());
  }
  let indented: Vec<String> = unformatted.iter().map(|p| format!("  {}", p)).collect();
  let listed = indented.join("\n");
  Err(format!(
    "error: not formatted:\n{}\nrun `voltaic fmt` on them\n",
    listed
  ))
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct FmtArgs {
  pub(crate) inputs: Vec<PathBuf>,
  pub(crate) check: bool,
  pub(crate) plain: bool,
}

pub(crate) fn parse_fmt_args(args: &[String]) -> Result<FmtArgs, String> {
  let mut inputs = Vec::new();
  let mut check = false;
  let mut plain = false;

  for arg in args {
    match arg.as_str() {
      "--check" => check = true,
      "--plain" => plain = true,
      flag if flag.starts_with('-') => {
        let help = usage();
        return Err(format!("error: unknown flag `{}`\n\n{}", flag, help));
      }
      path => inputs.push(PathBuf::from(path)),
    };
  }

  if inputs.is_empty() {
    let help = usage();
    return Err(format!("error: fmt needs at least one .sdx\n\n{}", help));
  }

  Ok(FmtArgs {
    inputs,
    check,
    plain,
  })
}