aboutsummaryrefslogtreecommitdiff
path: root/log.sh
diff options
context:
space:
mode:
authorChloƩ Vulquin <code@toast.bunkerlabs.net>2026-01-14 22:22:22 +0100
committerChloƩ Vulquin <code@toast.bunkerlabs.net>2026-01-15 06:37:04 +0100
commit74e70ef6e33cc57a1077892deccf6424199ac7ab (patch)
tree3ba24598a6bb4226b188f060154092d0db4313b0 /log.sh
initial importshlib
Diffstat (limited to '')
-rw-r--r--log.sh204
1 files changed, 204 insertions, 0 deletions
diff --git a/log.sh b/log.sh
new file mode 100644
index 0000000..bb979b5
--- /dev/null
+++ b/log.sh
@@ -0,0 +1,204 @@
+#!/bin/sh
+# this file is licensed under any of the following SPDX licenses
+# to be chosen by the user:
+# * 0BSD (https://spdx.org/licenses/0BSD.html)
+# * BlueOak-1.0.0 (https://blueoakcouncil.org/license/1.0.0)
+# * CC0-1.0 (https://creativecommons.org/publicdomain/zero/1.0/)
+# * Unlicense (https://unlicense.org/)
+
+## log.sh - Bunker Log
+# This is the bunker logging system for POSIX sh.
+# It provides you with stack-based logging semantics,
+# and includes advances features like stdin-forwarding.
+# It should not leak any environment variables,
+# besides those documented, and potentially `add` on buggy shells.
+# Everything here should be POSIXLY correct.
+# If it isn't, let me know! <toast (at) bunkerlabs.net>
+
+## Usage
+# Call `log some text here` or `something_with_output | log_stdin`.
+# It will then be formatted under the current log tree.
+#
+# You can manipulate the log tree using log_push and log_pop.
+# log_push can have multiple levels pushed on it at once.
+# For example, `log_push one two` from the default tree state will result
+# in the new tree being blog/one/two.
+# log_pop can pop multiple levels at once. To undo the above example, you could
+# call `log_pop 2` instead of calling log_pop twice.
+#
+# Whenever the log tree changes, the next call to `log` or `log_stdin` will
+# cause the state of the log tree to be printed in full.
+
+## Customizing
+# You can customize the "root" node of the log tree by setting log_tree to a
+# single word (no whitespace as per IFS) before you source this.
+# You can set a hard limit to the maximum length of any log level by setting
+# log_maxlen. This will truncate log levels when they are pushed.
+# You can also force padding on the output by setting log_minlen.
+# If log_minlen is negative, it will right-pad, like with printf.
+# You can change the way truncation is done by changing log_tstyles,
+# which is a space-delineated list of strategies.
+
+## API Summary
+# The following is a summary of all user-facing functions and environment
+# variables. Functions are denoted with `(args...)`s.
+# * log(msg...): log a message
+# * log_stdin(): log lines from stdin; do not use on interactive programs
+# * log[_stdin](v|q): log only if VERBOSE is set / QUIET is not set
+# * log_push(level...): push levels on the log tree
+# * log_pop(amount?): pop levels off the log tree; amount defaults to 1
+# * log_shift(level...): push levels on the log tree after popping that amount
+# | equivalent to `log_pop $#; log_push "$@"`
+# | with word splitting
+# * log_reset(): pop all levels except the root
+# * log_tree: initial state of the log tree, set this before sourcing this file
+# | to set the default (not removable) log level
+# * log_minlen: the minimum length of a log level. Levels that are too short
+# | will be space-padded. Padding inserted to the right if negative
+# * log_maxlen: the maximum length of a log level. Levels that are too long
+# | will be truncated
+
+# you can initialize your "root" to anything by setting log_tree before
+# you source this
+# the default is "blog" - bunker log
+: ${log_tree:=blog}
+log_indent=0
+
+# shadow tree, used to calculate indent
+# if you push, don't log, and then pop, you shadow tree will = your tree
+# => no need to recalculate indents
+# in short, if shadow tree doesn't match the tree,
+# the indent is recalculated at log-time
+log_stree=$log_tree
+
+# this is a function that allows for selecting different styles of truncation
+# $log_tstyles will be tried in a row until the result fits
+# as such, it's highly recommended the final option be a terminal one
+# available styles, with *s being terminal:
+# * cut*: truncates rightwards
+# * rcut*: truncates leftwards
+# * vowels: removes all vowels
+# * numbers: remove all numbers
+# * noalnum: remove everything other than alphanumerics
+: ${log_tstyles:=cut}
+log_truncate() (
+ set -- "$*" "$(printf "$*" | wc -c)" \
+ "$(echo "$log_tstyles" | cut -d' ' -f1)"
+ if [ "${log_maxlen:-$2}" -ge "$2" ]; then
+ echo "$1"
+ return 0
+ fi
+ case "$3" in
+ cut)
+ echo "$1" | cut -c 1-"${log_maxlen:-$2}"
+ return 0
+ ;;
+ rcut)
+ start=$(( $2 - ${log_maxlen:-$2} + 1 ))
+ [ "$start" -lt 1 ] && start=1
+ echo "$1" | cut -c "$start"-
+ return 0
+ ;;
+ vowels)
+ set -- "$(echo "$1" | sed -e 's/[aeiou]//g')"
+ ;;
+ numbers)
+ set -- "$(echo "$1" | sed -e 's/[0-9]//g')"
+ ;;
+ noalnum)
+ set -- "$(echo "$1" | sed -e 's/[^0-9a-zA-Z]//g')"
+ ;;
+ *) return 1 ;;
+ esac
+ # goto next method
+ log_tstyles=$(echo "$log_tstyles" | cut -d' ' -f2-)
+ log_truncate "$1"
+)
+
+# you can push multiple levels at once
+# if a level has embedded spaces, it counts for multiple levels
+# all words will be truncated to $log_maxlen, but only if it's set
+log_push() {
+ # do word splitting in case of extra embedded " "s
+ set -- "$*"
+ for add in $1; do
+ # truncate to log_maxlen, if it's set
+ set -- "$@" "$(log_truncate "$add")"
+ done
+ shift
+ log_tree="$log_tree $@"
+}
+
+# you can pop multiple levels at once, $1 is number to pop
+log_pop() {
+ # default to popping 1
+ # save the number of words in the log tree
+ set -- ${1:-1} $(echo "$log_tree" | wc -w)
+
+ # never pop the last word
+ if [ $1 -ge $2 ]; then
+ set -- $(( $2 - 1 )) $2
+ fi
+ log_tree=$(echo "$log_tree" | cut -d' ' -f 1-$(( $2 - $1 )) )
+}
+
+log_shift() {
+ # perform word splitting
+ set -- "$*"
+ set -- $1
+
+ log_pop $#
+ log_push "$@"
+}
+
+log_reset() {
+ set -- $(echo "$log_tree" | wc -w)
+ log_pop $(( $1 - 1 ))
+}
+
+# this will read stdin line by line
+# to integrate external commands into the log
+log_stdin() {
+ while read -r line; do
+ log "$line"
+ done
+}
+
+# echo "$*", but with
+# every level will be padded to $log_minlen, if it's set
+# you can set it to a negative number to right-pad it
+log() {
+ # we need to recalculate the indent
+ if [ "$log_indent" -eq 0 ] || [ "$log_tree" != "$log_stree" ]; then
+ # update the shadow tree and calculate the indentation level
+ log_stree=$log_tree
+ log_indent=$(printf "%${log_minlen}s/" $log_tree | wc -c)
+
+ # prefix printing
+ printf "%${log_minlen}s/" $log_tree
+ printf "\b: "
+ else
+ # indent up to the indent, but print a | in place of the :
+ # the 2 is because the above has an off-by-one, and this is easier
+ printf ' %.0s' $(seq 2 "$log_indent")
+ printf '| '
+ fi
+ echo "$*"
+} >&2
+
+# *v and *q variants
+logv() {
+ [ -n "$VERBOSE" ] && log "$@"
+}
+
+logq() {
+ [ -z "$QUIET" ] && log "$@"
+}
+
+log_stdinv() {
+ [ -n "$VERBOSE" ] && log_stdin "$@"
+}
+
+log_stdinq() {
+ [ -z "$QUIET" ] && log_stdin "$@"
+}