Skip to content

Commit

Permalink
piping and writing scripts
Browse files Browse the repository at this point in the history
  • Loading branch information
Drew Tada authored and Drew Tada committed Jan 30, 2024
1 parent 64441dc commit 6230916
Show file tree
Hide file tree
Showing 2 changed files with 136 additions and 7 deletions.
14 changes: 8 additions & 6 deletions src/cookbook/writing_scripts.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ When writing a script, you cannot control the `OnExit` behavior like you can wit
## Writing a Script
Let's look at the simplest possible script: `echo`, which takes in an argument, and prints it out again:
```rust
use kinode_process_lib::{await_next_request_body, call_init, println, Address};
use kinode_process_lib::{await_next_request_body, call_init, println, Address, Response};

wit_bindgen::generate!({
path: "wit",
path: "../../../wit",
world: "process",
exports: {
world: Component,
Expand All @@ -27,10 +27,7 @@ fn init(_our: Address) {
return;
};

println!(
"{}",
String::from_utf8(args).unwrap_or("echo: error".into())
);
let _ = Response::new().body(args).send();
}
```
From writing applications, this should look very familiar - the imports, `wit_bindge::generate!`, `call_init!`, `init(our: Address)`, etc. are all exactly the same.
Expand All @@ -40,6 +37,11 @@ Next, all we do is `String`ify the message body, and print it out.

Arbitrary logic can be put below `await_next_message_body` - just like an app, you can fire-off a number of requests, choose to await their responses, handle errors, etc. just like normal.

In this case, we send a `Response` containing the arguments passed in.
The `Response` let's us compose `echo` with other scripts via [piping](../terminal.md#piping-and-composing-scripts).
Not every script needs to end with a `Response`.
It would be valid to simply `println` the `args` and terminate, but this would mean that our script has no "return value" to compose with other scripts via pipes.

## Publishing a Script
Unlike processes accociated with a long-running application, which will be put into the `manifest.json`, scripts must be registered in a separate `scripts.json` file.
While very similar, there are a few important differences; let's take a look at an example that could live in your packages `pkg/scripts.json` file:
Expand Down
129 changes: 128 additions & 1 deletion src/terminal.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Terminal

## Basic Usage and Utilities
## Commands

All commands in the terminal are calling scripts - a special kind of process.
KinodeOS comes pre-loaded with a number of scripts useful for debugging and everyday use.
Expand Down Expand Up @@ -77,3 +77,130 @@ Example:
```

For more information on writing your own scripts, see the [cookbook](./cookbook/writing_scripts.md).

## `scripts.json`
For your scripts to be usable by the terminal, you must include a `pkg/scripts.json` file.
The JSON object in `scripts.json` describes a configuration for a set of WebAssembly (WASM) modules.
Each top-level key represents the path of the WASM module in your package, usually just `"myscript.wasm"`, `"echo.wasm"`, etc.

The value for each module is an object that specifies the configuration for that particular module.
The object can contain the following fields:

- `root` (Boolean): Indicates whether the script has root privileges
- `public` (Boolean): Determines if the script is publicly accessible by other processes
- `requestNetworking` (Boolean): Specifies whether the script will get networking capabilities
- `requestCapabilities` (Array): An array that lists the capabilities requested by the script. Each element in the array can be either a string or an object. The string represents a `ProcessId` that this script will be able to message. When an object is used, it specifies a different kind of capability from `issuer` with `params` as an arbitrary json object.
- `grantCapabilities` (Array): An array `ProcessId`s as strings which represent which processes will be able to message this script back

Modules may not necessarily use all these fields. For instance, "m.wasm" only uses root, public, and requestNetworking, omitting requestCapabilities and grantCapabilities.

### Example
This is the actual `scripts.json` file for the default `terminal:sys` scripts:
```json
{
"alias.wasm": {
"root": false,
"public": false,
"requestNetworking": false,
"requestCapabilities": [
"terminal:terminal:sys"
],
"grantCapabilities": []
},
"echo.wasm": {
"root": false,
"public": false,
"requestNetworking": false,
"requestCapabilities": [],
"grantCapabilities": []
},
"cat.wasm": {
"root": false,
"public": false,
"requestNetworking": false,
"requestCapabilities": [
"vfs:distro:sys",
{
"process": "vfs:distro:sys",
"params": {
"root": true
}
}
],
"grantCapabilities": []
},
"hi.wasm": {
"root": false,
"public": false,
"requestNetworking": true,
"requestCapabilities": [
"net:distro:sys"
],
"grantCapabilities": [
"net:distro:sys"
]
},
"top.wasm": {
"root": false,
"public": false,
"requestNetworking": false,
"requestCapabilities": [
"kernel:distro:sys"
],
"grantCapabilities": []
},
"m.wasm": {
"root": true,
"public": false,
"requestNetworking": true
}
}
```

## Piping and Composing Scripts
### Using Pipes in the Terminal
The terminal lets you pipe the previous command's output into another script.
For exapmle:
```bash
echo default-router-1.os Hello! |1 hi
```
Since the `echo` script just returns its arguments, we are piping the string `default-router-1.os Hello!` into `hi`, so the end result is just
```bash
hi default-router-1.os Hello!
```
This is exactly the same as a unix pipe except for the number that comes after the pipe.
This number after the pipe represents timeout in seconds.
In this case: wait for `echo` to respond, and if it does not after 1 second, abort.
Any `u64` value is a valid timeout value.

### Writing Your Script to be Pipe-able
Not all scripts are pipable.
For a script's output to be pipeable you must take the output and `.send()` it in a `Response`.
The [cookbook](./cookbook/writing_scripts.md) describes how every script begins with a `Request` that contains arguments in the `body`.
If you want the outputs of your script to be composable via pipes, you must terminate your script with a `Response` to this initial request.
As a simple example, let's look at the `echo` code:
```rust
use kinode_process_lib::{await_next_request_body, call_init, println, Address, Response};

wit_bindgen::generate!({
path: "../../../wit",
world: "process",
exports: {
world: Component,
},
});

call_init!(init);

fn init(_our: Address) {
let Ok(args) = await_next_request_body() else {
println!("echo: failed to get args, aborting");
return;
};

let _ = Response::new().body(args).send();
}
```

All we do here is get the args, and send a response back with the exact same args.
If instead of sending a `Response` we simply `println`d the arguments out, then we wouldn't be able to compose `echo` with pipes.

0 comments on commit 6230916

Please sign in to comment.