1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
//! Run bash under instrumentation, and hear what it says.
//!
//! ```bash
//! BC_INSTR DEPLOY say REC compiled "$target" # ship an arglist and carry on
//! BC_INSTR DEPLOY ask which target # ship one, block, run the answer
//! ```
//!
//! 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.
//!
//! ```no_run
//! 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. Definitions only:
//! /// sourcing this joins nothing.
//! fn bash(&self, _at: &Layout) -> String {
//! "STAGE() { BC_INSTR DEPLOY 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", ["-g", "target=staging"]),
//! _ => Answer::unknown(),
//! })
//! }
//!
//! async fn finish(self) -> Result<Self, Failure> { Ok(self) }
//! }
//!
//! impl Driving for Deploying {}
//!
//! # #[tokio::main(flavor = "current_thread")]
//! # async fn main() -> Result<(), Failure> {
//! // 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());
//! }
//! # Ok(())
//! # }
//! ```
//!
//! 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`.
//!
//! | | |
//! |---|---|
//! | `attended` | [`Layout`], [`Attended`], [`Kept`], [`Said`], [`heard`] |
//! | `session`, `attend` | the conversation: the workspace, the control fifo, one task per shell |
//! | `watch` | the descriptor a session ends on |
//! | `driving`, `serving` | the two roles, and what each hands back |
//! | `wire` | [`Message`], [`Answer`], and the protocol that carries them |
pub
use Arc;
pub use ;
pub use ;
pub use ;
pub use ;
pub use crate;
pub use crateShell;
/// What bash a rig gives the subject, and how a reaction is made once a
/// shell is there.
///
/// A description: `&self` throughout, because nothing about it changes by
/// running. **No method has a default body.**
///
/// | it is handed | it produces |
/// |---|---|
/// | [`&Layout`](Layout) — the workspace, and the files in it | [`Self::Reaction`](Rig::Reaction) |
/// | [`Arc<Shell>`](Shell) — `bash: Bash`, `options: Options`, `brought`, `joined: Stamp` | |
///
/// The rig's bash is laid beside the protocol's own by the session;
/// [`stack::with_walk`](crate::stack::with_walk) composes it where the
/// rig reports a frame walk.
// ANCHOR: rig-trait
// ANCHOR_END: rig-trait
/// One shell's reaction, for as long as that shell can speak.
///
/// It runs as a task of its own, so it owns what it holds (`'static`) and is
/// never sent to another thread. Awaiting inside a method yields to the other
/// shells' tasks; synchronous work blocks them for its duration.
///
/// | | |
/// |---|---|
/// | [`hear`](Reacting::hear) | a [`Message`] nobody is waiting on |
/// | [`answer`](Reacting::answer) | one the shell blocks on; the task writes the [`Answer`] back to it |
/// | [`finish`](Reacting::finish) | what is left, which lands in [`Attended::kept`] |
///
/// **No method has a default body.** The two implementations below are the
/// templates to copy.
// ANCHOR: reacting-trait
// ANCHOR_END: reacting-trait
/// A reaction that keeps every message, and has no answer to any of them.
/// A reaction that keeps nothing and answers nothing.