Skip to main content

Module rig

Module rig 

Source
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_ASK

A 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 shellshow they find the addresswhat the session lasts forwhat comes back
Drivingthe run, in a process group of its ownexactly what the run’s environment closure returned — Layout::bash_env with a stated Provision the usual pairthat process groupRun, with the subject’s ExitStatus
Servinga bash script, which named and made the workspace and started the serverits own choice: it feeds the same directory to start, probe, load and initiatewhoever holds the handleServed

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.

attendedLayout, Attended, Kept, Said, heard
session, attendthe conversation: the workspace, the control fifo, one task per shell
watchthe descriptor a session ends on
driving, servingthe two roles, and what each hands back
wireMessage, Answer, and the protocol that carries them

Re-exports§

pub use crate::failure::Doing;
pub use crate::failure::Failure;
pub use crate::shell::Shell;

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§

ExitStatus
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_env caller 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 value pair with this key — a convention clients may write their payload in, unrelated to the key=value headers 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.