Expand description
Run bash under instrumentation, and hear what it says.
declare -- BC_SAY__ARG_LABEL=DEPLOY
BC_SAY REC compiled "$target" # ship an arglist and carry on
declare -- BC_ASK__ARG_LABEL=DEPLOY # ask: block, then run the
declare -a BC_ASK__ARGS=(which target) # answer here, in this frame
BC_ASKA Rig is a description: the bash it gives the subject, where the
session’s files go, and how to build a reaction once a shell is there. The
reaction is Reacting, and it is made per shell, at the moment that
shell announces itself — so which bash it is, how it was started and what
it had switched on are members from construction, never parameters. Every
shell has a pipe of its own and a task of its own, so serving many shells
is many straight-line loops that interleave.
use std::sync::Arc;
use bash_interop::rig::{
Answer, Driving, Failure, Layout, Message, Provision, Reacting, Rig, Shell,
};
/// Keeps what one shell said, and tells it to use staging.
struct Deploying;
struct Told { shell: Arc<Shell>, heard: Vec<Message> }
impl Rig for Deploying {
type Reaction = Told;
/// A word the subject's scripts can call, as one command over the
/// core's, so it composes where any command does. Definitions only:
/// sourcing this joins nothing.
fn bash(&self, _at: &Layout) -> String {
"alias STAGE='BC_SAY__ARG_LABEL=DEPLOY BC_SAY STAGE'\n".to_string()
}
async fn joined(&self, _at: &Layout, shell: Arc<Shell>) -> Result<Told, Failure> {
Ok(Told { shell, heard: Vec::new() })
}
}
/// The standard initiation — data the run's closure hands to `bash_env`;
/// run only where a client or a provisioned file says so.
fn deploy_join(at: &Layout) -> String {
format!("BC_JOIN DEPLOY {}\n", bash_strings::emit_scalar(at.text()))
}
impl Reacting for Told {
type Kept = Self;
async fn hear(&mut self, said: Message) -> Result<(), Failure> {
self.heard.push(said);
Ok(())
}
async fn answer(&mut self, asked: Message) -> Result<Answer, Failure> {
Ok(match asked.words.first().map(String::as_str) {
Some("target") => Answer::of("declare", ["target=staging"]),
_ => Answer::unknown(),
})
}
async fn finish(self) -> Result<Self, Failure> { Ok(self) }
}
impl Driving for Deploying {}
// The closure's return is the subject's whole environment. Provisioning
// a joining file is the one auto-initiation there is, and it is stated
// here, at the fringe — every other shell's initiation is its own code.
let ran = Deploying
.run(&["bash", "deploy.bash"], |at| {
Ok(vec![at.bash_env(Provision::Joining(&deploy_join(at)))?])
})
.await?;
for shell in ran.whole()?.shells {
println!("pid {} said {} things", shell.shell.pid, shell.kept.heard.len());
}Who started the shells is a second question with exactly two answers, and each is a trait that carries its own orchestration:
| who started the shells | how they find the address | what the session lasts for | what comes back | |
|---|---|---|---|---|
Driving | the run, in a process group of its own | exactly what the run’s environment closure returned — Layout::bash_env with a stated Provision the usual pair | that process group | Run, with the subject’s ExitStatus |
Serving | a bash script, which named and made the workspace and started the server | its own choice: it feeds the same directory to start, probe, load and initiate | whoever holds the handle | Served |
Either way, the address is the workspace directory. Loading its laid
files defines; initiation is the client’s own line — except where a
provisioned bash_env.bash states Provision::Joining, the one
auto-initiation there is. The book’s docs/joining.md shows every way
a script joins, each as a whole script.
A session lasts as long as anyone who could still speak. Nothing inside a rig ends one.
The session is single-threaded: one current_thread runtime, one task per
shell, and no Send bound anywhere. What shells share — a sink, a merged
view — is the caller’s own, handed in through Rig::joined as an
Rc<RefCell<_>> or whatever it likes; a RefCell borrow must not be held
across an .await.
Re-exports§
Structs§
- Answer
- What a blocked shell is told to run next: one command, as an arglist — the same shape a message has, encoded the same way.
- Attended
- One shell, what its reaction left behind, and when it went.
- Layout
- The session’s workspace: the one coordinate, and the model of the files in it. Construction proves what every user needs: the directory exists (canonical), and is one line of text — it crosses into bash. Handed to every reaction at construction, since the instrument’s own frames name a file in here.
- Message
- What one shell’s client said, once.
- Micros
- Pid
- Run
- What a driven run produced.
- Said
- One message, and the shell that sent it.
- Served
- What a served session produced.
- Stamp
- When one line was written and when it was read.
- Whole
- A run that closed cleanly.
Enums§
- Exit
Status - How bash ended.
wait(2)yields exactly one of these. - Provision
- What the provisioned file does about the channel — the first thing a
Layout::bash_envcaller states. - Verb
- Whether the shell is waiting for something back. A word outside this set is a defect in the bash, never a client’s choice: a client’s own tag is a payload word, and the protocol never reads one.
Traits§
- Driving
- A rig whose run Rust orchestrates. The impl block is empty: the whole contract is the two provided entries.
- Reacting
- One shell’s reaction, for as long as that shell can speak.
- Rig
- What bash a rig gives the subject, and how a reaction is made once a shell is there.
- Serving
- A rig a running bash may attach to.
Functions§
- field
- Value of the first
key valuepair with this key — a convention clients may write their payload in, unrelated to thekey=valueheaders the protocol puts in front of one. - heard
- Everything the shells said, in the order it was said: by the sending shell’s own clock, stably over join order and each shell’s own order.
Type Aliases§
- Kept
- What one shell’s reaction leaves behind, for a given rig.