modular bash profile scripting with shellswain

When I set out to build a history module for my bashrc/profile, the dearth of code re-use and collaborative ~unixy modularity struck me as a little sad.

I knew early on that I wanted to abstract its logistics layer out into a lightweight scaffold for others to build on, but the vision for shellswain didn't really crystalize until I stumbled on bashup.events. (I wrote previously at neighborly Shell with bashup.events about how the the event-based abstractions it provides are fertile for building modular code.)

what does shellswain do?

shellswain is a small event-based scaffold that helps bashrc/profile scripts row in the same direction, minimize duplicate effort, and share scarce resources (like signal traps and PROMPT_COMMAND). It:

  • publishes a few key events that modules can use to initialize, react to command invocations, and clean up on exit
  • enables modules to publish and subscribe to their own events as well
  • tracks and exposes some basic metadata about the timing, content, and result of the most-recent command run
  • bundles per-script trap/signal namespacing provided by comity

shellswain is a little bit more than bash-preexec, but it isn't a full shell profile framework (if a full shell framework is Django, shellswain is more like Flask).

examples

You can find more in the repo, but I'll run over two quick usage examples. First, here's how you could ring the terminal bell any time a command runs for more than a minute:

long_command.bash (src)
1# notify on completion of a long-running command
2source shellswain.bash
3
4__ring_bell_after_long_commands(){
5  # 1 minute in microseconds
6  [[ ${swain[duration]} -gt 60000000 ]] && echo -e "\a"
7}
8
9event on swain:after_command __ring_bell_after_long_commands

A more compelling use of these event-based abstractions is enabling different modules or plugins to independently register cleanup functions without needing to cooperate much:

arbitrary_cleanup.bash (src)
 1# register cleanup functions as needed
 2source shellswain.bash
 3
 4load_plugins(){
 5  for plugin in "$@"; do
 6    source "plugins/$plugin" "$@"
 7  done
 8}
 9
10load_plugins "mysql" "postgres" "redis"
11
12# -- plugins/mysql --
13start_mysql(){
14  echo "pretend we're starting mysql"
15  event on swain:before_exit stop_mysql
16}
17stop_mysql(){
18  echo "pretend we're stopping mysql"
19}
20
21# -- plugins/postgres --
22start_postgres(){
23  echo "pretend we're starting postgres"
24  event on swain:before_exit stop_postgres
25}
26stop_postgres(){
27  echo "pretend we're stopping postgres"
28}
29
30# -- plugins/redis --
31start_redis(){
32  echo "pretend we're starting redis"
33  event on swain:before_exit stop_redis
34}
35stop_redis(){
36  echo "pretend we're stopping redis"
37}

If these are a little too synthetic, you can also see how I use shellswain in bashrc.nix and shell-hag.

Discussing this elsewhere?
Enter 'link' for a markdown link
or 'tweet <message>' for a pre-populated Tweet :)
Want to subscribe? Enter 'rss' for a feed URL.
>