use crate::build_command::build_command;
use crate::fmt_command::check_command;
use crate::fmt_command::fmt_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),
"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 .sdx 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 .sdx to .xrs",
state: State::Ready,
},
Command {
name: "check",
summary: "transpile, then report rustc's errors against the .sdx",
state: State::Ready,
},
Command {
name: "fmt",
summary: "format .sdx files in place, or --check them",
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 __rdx_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_six_entries() {
assert_eq!(
COMMANDS.len(),
6,
"doc 01 §27 lists six commands; adding a seventh 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"));
}
}