## Context-based programming

There's a style of programming that I stumbled across a while ago. I don't remember hearing about it anywhere else before, so I thought I'd write about it here.

I call it "context-based programming." Code is expressed using terms that are defined by a context, and the user can control the context and how the terms are defined.

Here's a simple example:

print(data)


Here, what happens when print is called depends on the context that defined print. It could mean to display the argument on a screen. It could also mean to send it to a printer.

If we allow a user to pick which context to use—the one that defines print to display on screen versus the one that defines it to send to a printer—then, the code is context-based.

Here's another example, this one from OpenRC, an alternative init system originating from Gentoo.

OpenRC init scripts are written in a subset of bash. Most actions are executed within functions that are pre-defined by the OpenRC system. Here's the script for the nullmailer service on my desktop system:

#!/sbin/runscript

# snip...

depend() {
use net logger
}

# snip...


The depend function is where a script declares its dependencies. The use keyword is one of the declarations that it can use. Here, it's declaring that this service uses the network and the system logger.

Nearly all of the code in this init script is contained in functions defined by OpenRC. This is important later.

Also, although the style is mostly declarative, it can still do traditional processing like this:

use net.*                       # Depend on every network interface

use service1                    # Always depend on service1

if [ condition ]
then
use service2                # Conditionally depend on service 2
fi

use $other # Depend on a service named in a variable  With this approach, OpenRC is able to do everything it needs for a service. It can start and stop services, and make sure to do so as required by dependencies, and in the correct order. ## Changing context Suppose I wanted to understand the dependencies of the services on my system a little better. Simply grepping them wouldn't work in all cases—OpenRC is actually too flexible for that, allowing other shell operations in or around declarations, as we've seen. Luckily, OpenRC scripts happen to allow me to control their context. Their default context is defined by the hash-bang line as coming from /sbin/runscript, but I can control how the script is loaded to avoid using that default context. Here's a program called services.sh, written in bash, that controls the context of an init script. The name of the init script is passed as the first argument. #!/bin/bash # Accepts a service name as the only argument. SERVICE="$1"

cd /etc/init.d  # This allows shell globbing in service scripts to match OpenRC's context.

source "$SERVICE" # read the service's definitions into my own context.  The source command loads the init script into the current context, but it does so without loading the init script's hash-bang line. Because all of the init script's code is in functions, including the code to start the service, I can load the script without interfering with my system's active services. I'll add a function to provide my own definition of use, then execute the depend function in the init script: function use { for s in "$@"
do
echo "$s ->$SERVICE"
done
}

depend


I should get a listing of all of the dependencies. And, I do:

net -> nullmailer
logger -> nullmailer


If I handle all of the dependency declarations that OpenRC supports, I can use Graphviz to generate a graph of the dependencies:

{
echo "digraph deps {"
for s in /etc/init.d/*
do
~/services.sh "$s" done echo "}" } | dot -Tpdf > deps.pdf  PDF of full graph Here's the complete script: #!/bin/bash # Accepts a service name as the only argument. SERVICE="$1"

cd /etc/init.d # This allows shell globbing in service scripts to match OpenRC's
# context.

function depend {
:
}

source "./$SERVICE" # Read the service's definitions into my own context. #$1: direction
# $@: list of services function dep { local dir="$1"; shift
for s in "$@" do if [ "$dir" = "->" ]
then
echo "\"$s\" -> \"$SERVICE\""
else
echo "\"$SERVICE\" -> \"$s\""
fi
done
}

function use {
dep "->" "$@" } function need { dep "->" "$@"
}

function after {
:
}

function keyword {
:
}

function before {
:
}

function provide {
dep "<-" "$@" } function config { : } function want { dep "->" "$@"
}

function shell_var {
:
}

function fstabinfo {
:
}

depend