import crate::build_command::build_command
import crate::fmt_command::check_command
import crate::fmt_command::fmt_command
import crate::sig_command::sig_command
import crate::watch_command::watch_command
import std::fmt::Write as _
export(crate) run : &[String] -> Result<(), String>
run args =
guard .map String::as_str (.first args) is Some first else
help = usage ()
print! \-> "#{help}"
return Ok ()
guard lookup first is Some command else
help = usage ()
return Err (format! \-> "error: unknown command `#{first}`\n\n#{help}")
if .not_yet command is Some message
return Err message
n = .len args
rest = &args[1 .. n]
case command.name of
"--help" ->
help = usage ()
print! \-> "#{help}"
Ok ()
"--version" ->
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! \-> "`#{other}` is Ready in COMMANDS but has no dispatch arm"
export(crate) data Command { export(crate) name &'static str, export(crate) summary &'static str, export(crate) state State }
deriving (Debug, Clone, Copy, PartialEq, Eq)
instance Command
export(crate) matches : &str -> &self -> bool
matches arg self =
if self.name == "--help"
return arg == "--help" || arg == "-h" || arg == "help"
if self.name == "--version"
return arg == "--version" || arg == "-V"
arg == self.name
export(crate) not_yet : &self -> Option<String>
not_yet self = case self.state of
State::Ready -> None
State::Planned { plan, id } ->
Some $ format! \-> "error: `#{self.name}` is not implemented yet — it is plan #{plan:02} (`#{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 }
test "a ready command has nothing to announce"
assert_eq! \-> .not_yet BUILD \-> None
test "a planned command names its plan"
message = FMT.not_yet ().expect "fmt is planned"
assert! \-> .contains "not implemented yet" message \-> "#{message}"
assert! \-> .contains "plan 34" message \-> "#{message}"
assert! \-> .contains "formatter" message \-> "#{message}"
test "the help and version aliases are accepted"
help = Command { name = "--help", summary = "", state = State::Ready }
for arg in ["--help", "-h", "help"]
assert! \-> .matches arg help \-> "#{arg}"
assert! \-> !.matches "--helpful" help
version = Command { name = "--version", summary = "", state = State::Ready }
assert! \-> .matches "--version" version && .matches "-V" version
assert! \-> !.matches "-v" version \-> "lowercase -v is not a version flag"
test "an ordinary command matches only itself"
assert! \-> .matches "build" BUILD
assert! \-> !.matches "builds" BUILD && !.matches "--build" BUILD
export(crate) data State
= Ready
#[allow(dead_code, reason = "the next declared-before-built command uses it; see above")]
| Planned { plan u32, id &'static str }
deriving (Debug, Clone, Copy, PartialEq, Eq)
export(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 }]
test "every command has a summary"
for command in COMMANDS
assert! \-> !.is_empty command.summary \-> "#{command.name} has no summary"
test "the surface has seven entries"
assert_eq! \-> .len COMMANDS \-> 7 \-> "doc 01 §27 listed six commands and doc 86 §8.1 adds `sig`; adding an eighth is a decision, not a patch"
export(crate) lookup : &str -> Option<&'static Command>
lookup arg = .find (\c -> .matches arg c) (.iter COMMANDS)
test "every declared command is reachable by its own name"
for command in COMMANDS
found = .unwrap_or_else (\-> panic! \-> "#{command.name} not found") (lookup command.name)
assert_eq! \-> found.name \-> command.name
test "an unknown argument selects nothing"
assert! \-> .is_none (lookup "frobnicate")
assert! \-> .is_none (lookup "")
test "no two commands claim the same argument"
for arg in ["build", "check", "fmt", "watch", "--version", "-V", "--help", "-h", "help"]
claimants = .count (.filter (\c -> .matches arg c) (.iter COMMANDS))
assert_eq! \-> claimants \-> 1 \-> "#{arg} is claimed #{claimants} times"
export(crate) usage : String
usage =
mut out = String::from "voltaic — a MoonScript-inspired surface for Rust\n\nusage:\n"
width = (COMMANDS.iter ().map (\c -> .len c.name)).max ().unwrap_or 0
for command in COMMANDS
name = command.name
_dropped = write! \-> out \-> " voltaic #{name:<width$} #{command.summary}"
case command.state of
State::Planned { plan, id } ->
_planned = write! \-> out \-> " (plan #{plan:02}, `#{id}`)"
State::Ready -> ()
out.push '\n'
out.push_str "\nCargo owns build, test, docs and dependencies (doc 01 §27).\n"
out
test "every command appears"
text = usage ()
for command in COMMANDS
assert! \-> .contains command.name text \-> "#{command.name} is missing from the usage text:\n#{text}"
assert! \-> .contains command.summary text \-> "#{command.name} summary missing"
test "no command advertises a plan"
text = usage ()
assert! \-> !.contains "plan " text \-> "every command is implemented, so none should name a plan:\n#{text}"
test "fits on one screen"
text = usage ()
lines = text.lines ().count ()
assert! \-> lines <= 24 \-> "usage is #{lines} lines, which no longer fits a terminal"
test "names cargo as the owner of everything else"
text = usage ()
assert! \-> .contains "Cargo owns" text