If you’ve been following the saga of my little Bash module — a tiny script that started as a joke, escaped into the wild, and now pretends to be a real piece of software — you know that we already published it on GitHub, gave it proper documentation, added tests, built a shiny installer, and even created an uninstaller (because apparently people like reversible actions).
But every hero’s journey needs a second act.
And every Bash module, no matter how small, eventually wakes up one morning and whispers:
“Hey… what if I had a new feature?”
So today, dear traveler, we’re adding one.
Grab your towel. Things are about to get nerdy.
1. Why Add a Stopwatch?
I wanted something fun, something useful, and something not too annoying.
(Three things that rarely appear together in Bash.)
The idea struck me:
A stopwatch that measures how long each shell command takes.
Every time you run something — ls, docker run, sudo rm -rf / (please don’t) — the module quietly records how long it took. Then, right before the next prompt appears, it prints a small timing note.
Something like:
⏱ Completed in: 143ms
$ _
Is it necessary?
No.
Is it cool?
Absolutely.
Is it a perfect excuse to teach feature development on GitHub?
You bet.
2. Planning the Feature (Yes, We Pretend to Be Professional)
Before writing anything, let's outline what has to be done:
✔ Add new stopwatch logic
This means:
- A function that records the start time before each command.
- A function that calculates the duration after each command.
- A display function that prints the result before the prompt.
Thanks to bash-preexec.sh, which already handles the “before” and “after” hooks for shell commands, integrating this feature is much easier.
✔ Add configuration options
Users must be able to turn the stopwatch on or off:
STOPWATCH_ENABLED="true"
Maybe later:
- Color output
- Precision control
- Display format options (“seconds”, “ms”, “galactic years”?)
But for now — on/off is enough.
✔ Add tests
We need:
- Unit tests for the stopwatch functions
- Tests verifying the timing appears in the console
- Tests for config on/off
- Tests for installer behavior (since dependencies are now required)
✔ Update the installer
The installer must check that:
datesupports nanoseconds (many shells do)bash-preexec.shis installed- configs are patched correctly
In other words:
It should confirm the user’s computer isn’t secretly a potato.
✔ Write documentation
No feature exists until it is documented.
Otherwise it’s just a rumor.
✔ Create a feature branch
We don’t dump code into main.
That way lies madness and broken terminals.
✔ Open a pull request
Where a future version of me — or you — can review it.
3. Creating a Feature Branch (The Official Developer Ritual)
We start with the classic Git spell:
git checkout -b feature/timers
And just like that, a new parallel universe exists.
In this universe:
- You can break the code
- Fix the code
- Break it again
- Add debugging printouts
- Cry
- Fix it again
…and none of it affects the main project.
This is the power of branches.
4. Writing the Stopwatch Logic (Now the Module Gets Smarter)
Thanks to bash-preexec.sh, adding timing hooks is easy.
Before the command runs:
hhgttg_preexec() {
if [[ "$STOPWATCH_ENABLED" == "true" ]]; then
COMMAND_START_TIME=$(date +%s%N)
fi
}
After the command finishes:
hhgttg_precmd() {
if [[ "$STOPWATCH_ENABLED" == "true" && -n "$COMMAND_START_TIME" ]]; then
local end_time=$(date +%s%N)
local elapsed=$(( (end_time - COMMAND_START_TIME) / 1000000 ))
printf "⏱ Completed in: %dms\n" "$elapsed"
fi
}
Boom.
Timing is now built into the module.
Will it work perfectly?
Probably not the first time.
But that’s why we have tests!
5. Adding a Config Switch (Power to the Users)
In hhgttg.config.sh:
# Enables or disables the stopwatch
STOPWATCH_ENABLED="true"
Users can toggle it:
STOPWATCH_ENABLED="false"
No need to uninstall the module just to stop the timer from judging your slow commands.
6. Updating the Installer (Because No Feature Stands Alone)
Now the installer needs to ensure timing is possible.
What might go wrong?
- The system has an outdated
datecommand without nanoseconds bash-preexec.shdidn’t download correctly- The config file is missing the new variable
- Someone is running the script on… macOS (brace for sed differences)
So we add:
command -v date >/dev/null 2>&1 || die "date command missing!"
And maybe:
if ! date +%s%N >/dev/null 2>&1; then
echo "Warning: nanosecond timing not supported. Stopwatch disabled."
fi
Installer updated.
Module smarter.
User slightly more prepared.
7. Writing Tests (Where Features Become Real)
Inside test/test_stopwatch.bats:
@test "stopwatch prints timing" {
export STOPWATCH_ENABLED="true"
run bash -c 'source hhgttg.sh; sleep 0.1'
assert_output --partial "Completed in"
}
Configuration test:
@test "stopwatch disabled" {
export STOPWATCH_ENABLED="false"
run bash -c 'source hhgttg.sh; ls'
refute_output --partial "Completed in"
}
Installer test:
@test "installer adds config entry" {
run ./install.sh
grep -q "STOPWATCH_ENABLED" "$HOME/.hhgttg/hhgttg.config.sh"
}
Now we break everything until every test passes.
Then celebrate.
8. Committing the Progress (One Small Commit for Bash, One Giant Leap for My Module)
git add .
git commit -m "Add stopwatch feature with config + tests + installer updates"
If tests fail, repeat the cycle:
break → cry → fix → commit.
9. Opening a Pull Request (The Moment of Truth)
Back on GitHub:
- Open Pull Requests
- Select feature/stopwatch → main
- Add title:
“Add stopwatch feature” - Add description:
What was added, why, how it works - Add checklist
- Add screenshots
- Add code snippets
If this were a team project, now someone else would review it.
But since I am the team — reviewer, developer, QA, designer, and motivational speaker — I simply review it myself, pretend I’m very strict, approve it, then merge.
10. Merge and Celebrate (Preferably With Coffee)
Once merged:
- The feature becomes part of the main code
- The documentation is live
- The tests validate everything
- The installer supports the new functionality
You now have a shiny new stopwatch in your shell.
And your Bash prompt is officially more useful (and more judgmental) than before.
Conclusion
Adding a new feature to a Bash module is not just typing random characters into your .sh file until something stops breaking (although sometimes it feels like that).
The real workflow is:
- Think through the feature
- Create a branch
- Write the feature
- Add configuration
- Update documentation
- Write tests
- Update the installer
- Commit in small steps
- Open a pull request
- Merge when ready
By following this flow, even small projects grow in a clean, professional, maintainable way — and you get a chance to learn real development practices while building something fun.