#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Door {
pub word: &'static str,
pub takes: &'static str,
pub arity: (usize, usize),
pub summary: &'static str,
pub detail: &'static str,
}
impl Door {
pub fn usage(&self) -> String {
if self.takes.is_empty() {
format!("lernie {}", self.word)
} else {
format!("lernie {} {}", self.word, self.takes)
}
}
pub fn refused(&self, got: usize) -> String {
let (least, most) = self.arity;
let takes = if least == most {
format!("{least}")
} else {
format!("{least} to {most}")
};
format!(
"`lernie {}` takes {takes} argument(s) and got {got} — usage: {}",
self.word,
self.usage()
)
}
}
pub fn find(word: &str) -> Option<Door> {
TABLE.iter().find(|door| door.word == word).copied()
}
pub fn table() -> Vec<Door> {
TABLE.to_vec()
}
pub const START: Door = Door {
word: "start",
takes: "<workspace> <goal> [<dir>] [--role <name>]",
arity: (2, 5),
summary: "begin a conversation on that workspace, optionally aimed at a directory and a role",
detail: "The start family's two acts (yog's REMOTE §8.1), staged and \
fired in one process. Not a gesture but both of them: a \
`prepare`, then a `prompt` carrying the body that answered it \
straight back. Both reply streams print; the exit code is the \
fire's. It is one word because the thing between the two acts is \
a local — the staged body, held while the second is composed — \
and a nested object is not a word an operator types, which is why \
it is a door here rather than a row of the gesture table. \
NAME A DIRECTORY and the start is staged on the path rung \
instead of the bare one: the conversation's driver runs THERE, \
and the engine's own target preamble is prepended to the goal. \
Without it the conversation is born wherever the engine puts a \
bare one, and a directory named in the goal's prose is a request \
the tools are free to ignore. The path is on the ENGINE's box and \
this seat does not check it; a path that is not there is refused \
over the wire, in the engine's own words. \
NAME A ROLE and the conversation is born on it (REMOTE §9.21) — \
the soul, the provider assignment and the tool grant the \
workspace's governing config declares under that name, which is \
how a planning conversation is started rather than a working one. \
It is stated on the FIRE and not on the stage, because which role \
you want is the choice made between the two acts. Without it the \
conversation is born on the engine's default, and a role the \
governing config does not declare is refused there, before any \
branch or worktree exists.",
};
pub const ASK: Door = Door {
word: "ask",
takes: "<envelope>",
arity: (1, 1),
summary: "one gesture envelope, written out",
detail: "Any op — including one this build has never heard of — as the \
JSON object the boundary carries, with `op` the discriminant and \
every parameter a named field. This is not a fallback: it is the \
surface, and the typed verbs are its shorthand. Quote it as one \
argument. It goes down the channel its `workspace` names, exactly \
as a typed verb does, and an op the far end does not know refuses \
in band naming it.",
};
pub const ENTRIES: Door = Door {
word: "entries",
takes: "",
arity: (0, 0),
summary: "describe every channel this box holds, without dialling any",
detail: "This box's own engine first, then one row per workspace held \
elsewhere, each with its address or the reason it has none. It \
opens no socket, so it answers with every engine down — and a \
half-provisioned channel says which file is missing beside the \
ones that are fine. Material reaches these directories by the \
operator's hand, out of channel, always.",
};
pub const OPS: Door = Door {
word: "ops",
takes: "[<max>]",
arity: (0, 1),
summary: "every act that crossed the boundary, newest last",
detail: "The trail of what an engine has DONE — one row per act, with the \
command it ran and how it ended. It names no workspace, so its \
subject is EVERY channel this box holds and the answer is their \
union. The depth is how many rows to ask for and it is optional: \
without one the word asks for the same depth the window's own \
trail pane does, which is deliberately larger than a screen \
because what an operator is looking for is the row before the one \
that broke.",
};
pub const HELP: Door = Door {
word: "help",
takes: "[<verb>]",
arity: (0, 1),
summary: "what a word takes and what it answers with",
detail: "With no argument, the usage and every word this binary answers. \
With one, that word's own page — a gesture verb or one of the \
four doors alike, because a page an operator is offered has to be \
reachable for every word the list names. Its subject is this \
binary rather than a world, so it answers with no engine up and \
no channel provisioned. `--help` and `-h` are the same word.",
};
const TABLE: &[Door] = &[START, ASK, OPS, ENTRIES, HELP];
#[cfg(test)]
mod tests;