The power of evolving abstractions blog home

Posted Thursday, 23-Jun-2022 by Ingo Karkat

It started with the addOrUpdate command two years ago; now there are 17 different variants covering the original lines, blocks of lines, and assignments, either just handling existing entities, or also adding new ones, with the possibility to query the user and remember their decision. Why has it been growing like that? Because I found more and more use cases.

With each added capability, things that used to be complex became easier, more understandable, and with that more robust and maintainable. Today, I found out that I could replace an early use of updateLine with a recent appendAssignment variant. This had evolved from updating whole lines to updating assignments in the form key=value to appending individual entries in the value part: key="part1 part2 part3".

This is an excerpt from the original script:

for pruneDir in "${dirspecs[@]}"
do
    literalPruneDirPattern="$(printf %s "$pruneDir" | sed -e 's/[][\$*.^]/\\&/g')"
    literalPruneDirReplacement="$pruneDir"
    literalPruneDirReplacement="${literalPruneDirReplacement//\\/\\\\}"
    literalPruneDirReplacement="${literalPruneDirReplacement//&/\\&}"

    checkedAddOrUpdate "${checkArg[@]}" \
        --sudo-command sudoWithUnixhome \
        --no-subsequent-backup \
        updateLine \
            --accept-match "^PRUNEPATHS=\"\\(.* \\)\\?${literalPruneDirPattern}\\( .*\\)\\?\".*\$" \
            --update-match '^\(PRUNEPATHS=".*\)\(".*\)$' \
            --replacement "\\1 ${literalPruneDirReplacement}\\2" \
            -- "$LOCATE_UPDATEDB_CONFIG_FILESPEC"; $statusCommand $?
done

There, I still had to deal with complex patterns to match and extract the other parts. The updateLine command already provided the file handling, checking whether an update is necessary, but the use here still had too much resemblance to an sed invocation (which still lies underneath those commands; standing on the shoulders of giants, as they say). Especially the necessary escaping for both pattern and replacement parts (something that an ad-hoc script likely would never have, and then cause unexpected failures when it's thrown into a different usage context years later — ask me how I know…) highlight the leaky abstraction and accidental complexity.

This is the replacement; note the lack of escaping (the appendAssignment command takes literal strings), and the use of domain language in the --lhs and --rhs arguments:

for pruneDir in "${dirspecs[@]}"
do
    checkedAddOrUpdate "${checkArg[@]}" \
        --sudo-command sudoWithUnixhome \
        --no-subsequent-backup \
        appendAssignment \
            --lhs PRUNEPATHS \
            --rhs "$pruneDir" \
            -- "$LOCATE_UPDATEDB_CONFIG_FILESPEC"; $statusCommand $?
done

A simple change, yet so powerful. And I didn't stop there. I realized another use for this command, and extracted that generic functionality into a separate, more generic command. At the end of the day, the original part of the script just delegates to that new abstraction:

exec setup-updatedb-prunedirs "${checkArg[@]}" -- "${dirspecs[@]}"

Ingo Karkat, 23-Jun-2022

blog comments powered by Disqus