A Structured Shell.
Monch is split up into a few different crates:
monch_shell: The shell itself. Provides themonchbinary.monch_io: A set of utilities for the shell andmonch-compatible programs to read and write objects from stdin and stdoutmonch_syntax: The shell's parser and grammar definition.monch_util_*: Utilities that work well withmonchget: Extract a value from a stream of objects by its path (similar tojq)grep: Filter a stream of objects by string matching (optionally on a nested field)ls: List files and their metadatased: Replace text in a stream of strings
If you don't have cargo or a Rust toolchain installed already, install rustup:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# ...and follow the promptsThen, to build the shell, all its dependencies, and all the utility binaries, run:
cargo build
To run the shell, run:
cargo run --bin monch
# (or ./target/{debug,release}/monch, depending on build profile)And to run the tests, run:
cargo testYou can run any command on your system in monch, and you'll get the output in your shell just like you normally would:
/ $ ps
PID TTY TIME CMD
292799 pts/1 00:00:00 bash
292837 pts/1 00:00:00 monch
293052 pts/1 00:00:00 psHowever, if you run a Monch-aware command (like our re-implentation of ls, for example), you'll see structured output instead:
/ $ ls -la
{name: etc, kind: Dir}
{name: mnt, kind: Dir}
{name: run, kind: Dir}
{name: var, kind: Dir}
{name: cdrom, kind: Dir}
{name: usr, kind: Dir}
{name: lib, kind: Unknown}
{name: srv, kind: Dir}
{name: root, kind: Dir}
{name: media, kind: Dir}
{name: snap, kind: Dir}
{name: sys, kind: Dir}
{name: boot, kind: Dir}
{name: opt, kind: Dir}
{name: lib32, kind: Unknown}
{name: home, kind: Dir}
{name: libx32, kind: Unknown}
{name: dev, kind: Dir}
{name: proc, kind: Dir}
{name: lib64, kind: Unknown}
{name: sbin, kind: Unknown}
{name: swapfile, kind: File}
{name: bin, kind: Unknown}
{name: tmp, kind: Dir}You can manipulate streams of objects:
- Use the
getcommand to extract a field - Use the
grepcommand to filter by a string field's contents
So, to get a list of only the directories in the root, you would run:
/ $ ls -la | grep -f .kind Dir
{name: etc, kind: Dir}
{name: mnt, kind: Dir}
{name: run, kind: Dir}
{name: var, kind: Dir}
{name: cdrom, kind: Dir}
{name: usr, kind: Dir}
{name: srv, kind: Dir}
{name: root, kind: Dir}
{name: media, kind: Dir}
{name: snap, kind: Dir}
{name: sys, kind: Dir}
{name: boot, kind: Dir}
{name: opt, kind: Dir}
{name: home, kind: Dir}
{name: dev, kind: Dir}
{name: proc, kind: Dir}
{name: tmp, kind: Dir}
{name: lost+found, kind: Dir}Then, if you wanted a list of only the names of directories in the root, you would run:
/ $ ls -la | grep -f .kind Dir | get .name
etc
mnt
run
var
cdrom
usr
srv
root
media
snap
sys
boot
opt
home
dev
proc
tmp
lost+foundYou could also save the file records you wanted into a file, and load them for use later:
/ $ ls -la | grep -f .kind Dir >files.cbor
/ $ get <files.cbor .name
etc
mnt
run
# ...etc.
/ $ get <files.cbor .name | sed etc 'something else here'
something else here
mnt
runThe shell can also catch common type errors, if it knows that you're attempting to pipe together two commands that expect different kinds of data:
/ $ ps | get
monch: type mismatch: cannot connect [unknown] (produced by ps) to cbor (expected by get)It will also fail with an error if you perform a an I/O redirection that ignores data, like one in the middle of the pipeline:
/ $ echo test >file | cat
monch: --> 1:11
|
1 | echo test >file | cat
| ^---^
|
= cannot redirect output unless it's from the last command in a pipeline
/ $ echo test | cat <file
monch: --> 1:12
|
1 | cat | echo <file
| ^---^
|
= cannot redirect input outside unless it's from the first command in a pipelineFor each commit, we run the test suite across Mac, Windows, and Linux in GitHub Actions.
We also make sure each commit successfully builds with --release and optimizations enabled.