voltaic-cli 0.2.0

The Voltaic compiler: .vlt in, .vrs containing ordinary Rust out.
use crate::build_command::build_command;

use crate::fmt_command::check_command;

use crate::fmt_command::fmt_command;

use crate::sig_command::sig_command;

use crate::watch_command::watch_command;

use std::fmt::Write as _;

pub(crate) fn run(args: &[String]) -> Result<(), String> {
  let Some(first) = args.first().map(String::as_str) else {
    let help = usage();
    print!("{}", help);
    return Ok(());
  };

  let Some(command) = lookup(first) else {
    let help = usage();
    return Err(format!("error: unknown command `{}`\n\n{}", first, help));
  };

  if let Some(message) = command.not_yet() {
    return Err(message);
  }

  let n = args.len();
  let rest = &args[1..n];

  match command.name {
    "--help" => {
      let help = usage();
      print!("{}", help);
      Ok(())
    }
    "--version" => {
      let version = env!("CARGO_PKG_VERSION");
      println!("voltaic {}", version);
      Ok(())
    }
    "build" => build_command(rest),
    "check" => check_command(rest),
    "fmt" => fmt_command(rest),
    "sig" => sig_command(rest),
    "watch" => watch_command(rest),
    other => unreachable!("`{}` is Ready in COMMANDS but has no dispatch arm", other),
  }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct Command {
  pub(crate) name: &'static str,
  pub(crate) summary: &'static str,
  pub(crate) state: State,
}

impl Command {
  pub(crate) fn matches(&self, arg: &str) -> bool {
    if self.name == "--help" {
      return arg == "--help" || arg == "-h" || arg == "help";
    }
    if self.name == "--version" {
      return arg == "--version" || arg == "-V";
    }
    arg == self.name
  }

  pub(crate) fn not_yet(&self) -> Option<String> {
    match self.state {
      State::Ready => None,
      State::Planned { plan, id } => Some(format!(
        "error: `{}` is not implemented yet — it is plan {:02} (`{}`)",
        self.name, plan, id
      )),
    }
  }
}

#[cfg(test)]
const FMT: Command = Command {
  name: "fmt",
  summary: "format a .vlt in place",
  state: State::Planned {
    plan: 34,
    id: "formatter",
  },
};

#[cfg(test)]
const BUILD: Command = Command {
  name: "build",
  summary: "transpile",
  state: State::Ready,
};

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum State {
  Ready,
  #[allow(
    dead_code,
    reason = "the next declared-before-built command uses it; see above"
  )]
  Planned {
    plan: u32,
    id: &'static str,
  },
}
#[allow(unused_imports)]
use State::*;

pub(crate) const COMMANDS: &[Command] = &[
  Command {
    name: "build",
    summary: "transpile a .vlt to .vrs",
    state: State::Ready,
  },
  Command {
    name: "check",
    summary: "transpile, then report rustc's errors against the .vlt",
    state: State::Ready,
  },
  Command {
    name: "fmt",
    summary: "format .vlt files in place, or --check them",
    state: State::Ready,
  },
  Command {
    name: "sig",
    summary: "print the judgement Lattice inferred for every definition; --verify, --emit, --semver and --require-signatures",
    state: State::Ready,
  },
  Command {
    name: "watch",
    summary: "rebuild on change",
    state: State::Ready,
  },
  Command {
    name: "--version",
    summary: "print the version",
    state: State::Ready,
  },
  Command {
    name: "--help",
    summary: "print this message",
    state: State::Ready,
  },
];

pub(crate) fn lookup(arg: &str) -> Option<&'static Command> {
  COMMANDS.iter().find(|c| c.matches(arg))
}

pub(crate) fn usage() -> String {
  let mut out = String::from("voltaic — a MoonScript-inspired surface for Rust\n\nusage:\n");

  let width = COMMANDS.iter().map(|c| c.name.len()).max().unwrap_or(0);
  for command in COMMANDS {
    let name = command.name;
    let _dropped = write!(out, "  voltaic {:<width$}  {}", name, command.summary);
    match command.state {
      State::Planned { plan, id } => {
        let _planned = write!(out, " (plan {:02}, `{}`)", plan, id);
      }
      State::Ready => (),
    };
    out.push('\n');
  }

  out.push_str("\nCargo owns build, test, docs and dependencies (doc 01 §27).\n");
  out
}

#[cfg(test)]
mod __vlt_tests {
  use super::*;

  #[test]
  fn a_ready_command_has_nothing_to_announce() {
    assert_eq!(BUILD.not_yet(), None);
  }

  #[test]
  fn a_planned_command_names_its_plan() {
    let message = FMT.not_yet().expect("fmt is planned");
    assert!(message.contains("not implemented yet"), "{}", message);
    assert!(message.contains("plan 34"), "{}", message);
    assert!(message.contains("formatter"), "{}", message);
  }

  #[test]
  fn the_help_and_version_aliases_are_accepted() {
    let help = Command {
      name: "--help",
      summary: "",
      state: State::Ready,
    };
    for arg in ["--help", "-h", "help"] {
      assert!(help.matches(arg), "{}", arg);
    }
    assert!(!help.matches("--helpful"));

    let version = Command {
      name: "--version",
      summary: "",
      state: State::Ready,
    };
    assert!(version.matches("--version") && version.matches("-V"));
    assert!(!version.matches("-v"), "lowercase -v is not a version flag");
  }

  #[test]
  fn an_ordinary_command_matches_only_itself() {
    assert!(BUILD.matches("build"));
    assert!(!BUILD.matches("builds") && !BUILD.matches("--build"));
  }

  #[test]
  fn every_command_has_a_summary() {
    for command in COMMANDS {
      assert!(
        !command.summary.is_empty(),
        "{} has no summary",
        command.name
      );
    }
  }

  #[test]
  fn the_surface_has_seven_entries() {
    assert_eq!(
      COMMANDS.len(),
      7,
      "doc 01 §27 listed six commands and doc 86 §8.1 adds `sig`; adding an eighth is a decision, not a patch"
    );
  }

  #[test]
  fn every_declared_command_is_reachable_by_its_own_name() {
    for command in COMMANDS {
      let found = lookup(command.name).unwrap_or_else(|| panic!("{} not found", command.name));
      assert_eq!(found.name, command.name);
    }
  }

  #[test]
  fn an_unknown_argument_selects_nothing() {
    assert!(lookup("frobnicate").is_none());
    assert!(lookup("").is_none());
  }

  #[test]
  fn no_two_commands_claim_the_same_argument() {
    for arg in [
      "build",
      "check",
      "fmt",
      "watch",
      "--version",
      "-V",
      "--help",
      "-h",
      "help",
    ] {
      let claimants = COMMANDS.iter().filter(|c| c.matches(arg)).count();
      assert_eq!(claimants, 1, "{} is claimed {} times", arg, claimants);
    }
  }

  #[test]
  fn every_command_appears() {
    let text = usage();
    for command in COMMANDS {
      assert!(
        text.contains(command.name),
        "{} is missing from the usage text:\n{}",
        command.name,
        text
      );
      assert!(
        text.contains(command.summary),
        "{} summary missing",
        command.name
      );
    }
  }

  #[test]
  fn no_command_advertises_a_plan() {
    let text = usage();
    assert!(
      !text.contains("plan "),
      "every command is implemented, so none should name a plan:\n{}",
      text
    );
  }

  #[test]
  fn fits_on_one_screen() {
    let text = usage();
    let lines = text.lines().count();
    assert!(
      lines <= 24,
      "usage is {} lines, which no longer fits a terminal",
      lines
    );
  }

  #[test]
  fn names_cargo_as_the_owner_of_everything_else() {
    let text = usage();
    assert!(text.contains("Cargo owns"));
  }
}