# cloyster: a shell wrapper for machine-oriented shell scripts
_cloyster_ helps you making small, as portable as possible, single-task focused shell scripts
that are primarily consumed by other scripts or machines.
_cloyster_ provides a portable utilitary functions library to help you be efficient transforming
and manipulating data.
_cloyster_ scripts are called "commands". A good command does a single thing/action
(list something, create an interface, bridge an interface, ...).
Being machine-oriented and automation-oriented, commands accepts arguments as JSON, and return
JSON: this also to easily combine and use commands in commands, filter outputs easily (using `jq`),
pipe them together...
**Base dependencies**: POSIX shell, jq, jo
## Scripting commands
A command is a POSIX shell script, using `/usr/local/libexec/cloyster` as shebang.
It will run in the _cloyster_ environment and has three outputs:
- the standards outputs, stdout and stderr, which acts as usual: unstructured, line based text, automatically
handled by _cloyster_ and returned as `output`,
- the structured JSON result of your command, set by calling `return_result`, returned as `result`.
- a structured JSON fatal error, set by calling `return_error`, returned as `error`.
### API
* `return_result JSON|JO` exits successfully with a JSON result
* `return_error JSON|JO [exit_code=1]` exists with exit_code and a JSON error
* `args_get [JQ_QUERY=.]` query the arguments using jq
### Utilitary Functions
* `ensure_command_exists BINARY` ensures a binary exists, exits with error if not
* `os_get [KEY=.|jq] [JQ_QUERY]` operating system version (type, name, pretty_name, version, version_id, supported)
* `make_random_identifier [SIZE=32]`
* `date_now_iso_utc`
* `posix_time_microseconds`
### Arguments/Input
Arguments, or inputs to the command, are also passed in JSON. You have three ways of passing the arguments:
* human friendly: `COMMAND arg1=something ...` (uses `jo`),
* escaped json string: `COMMAND '{"something": true}'`
* json wrapped in base64: `COMMAND -b eyJiYW5uZXIiOiJiZWFzdGllIGZvciB0aGUgd2luIn0K`
### Output
```js
{
"error": null /* Error */,
"exit_status": 0,
"command": "./commands/ifconfig-interfaces-list.sh",
"arguments": {},
"duration_microseconds": /* microseconds */,
"started_at_utc": /* ISO8061Z */,
"finished_at_utc": /* ISO8061Z */,
"result": /* any */,
"output": [
// Output Line
]
}
```
Output Line:
```js
{"output": "stdout|stderr", "date":"ISO8061Z", line: "...."}
```
Error:
```js
{
"type": "execution|command",
"error": "error_name",
// Error specific field
}
```
### Reading script from stdin
You can tell _cloyster_ to execute a script from stdin by setting the `-s` or `-` option to `cloyster`:
```sh
cat commands/ifconfig-interfaces-list | cloyster -s
cloyster - <<EOF
json=$(jls --libxo json | jq -r '.["jail-information"].jail | @json')
set_result "${json}"
EOF
```
### Examples
#### Example: `commands/ifconfig-interfaces-names.sh`
```sh
#!/usr/local/libexec/cloyster
echo "stdout something"
>&2 "stderr something"
set_result $(ifconfig | grep -E "^[a-z0-9_]+:" | cut -d ':' -f 1 | jq -nRr '[inputs | select(length>0)] | @json')
```
Run it (piped to `jq` so you beautify JSON output):
```sh
./commands/ifconfig-interfaces-names.sh | jq
```
Result:
```json
{
"error": false,
"exit_status": 0,
"command": "./commands/ifconfig-interfaces-list.sh",
"arguments": [],
"duration_microseconds": 67,
"started_at_utc": "2022-03-22T09:28:51Z",
"finished_at_utc": "2022-03-22T09:28:51Z",
"result": ["eth0", "eth1", "lo0"],
"output": [
{"output": "stdout", "date":"iso8601 utc", line: "stdout something"},
{"output": "stderr", "date":"iso8601 utc", line: "stderr something"}
]
}
```
## Code guide
meh
* We write POSIX Shell compliant code.
* Use `shellcheck`.
* Variables:
* are considered private/local if they begin by a `_`
* are considered public if they are in lowercase
* are exported/env if they are in uppercase
* Cloyster itself must be portable (POSIX Shell) and should be able to work on macOS, Linux, ...
*