@@ -13,24 +13,25 @@ __LOG_SH_LOADED=1
1313# LOG_LEVEL=20 # INFO and above
1414# LOG_TIME=1 # enable timestamps
1515# LOG_PREFIX="mytool" # prefix tag
16- # LOG_COLOR=1 # enable ANSI color (best-effort)
1716
1817: " ${LOG_LEVEL:= 20} " # 10=DEBUG, 20=INFO, 30=WARN, 40=ERROR
1918: " ${LOG_TIME:= 0} " # 0/1
2019: " ${LOG_PREFIX:= } " # e.g. "mytool"
21- : " ${LOG_COLOR:= 0} " # 0/1 (best-effort)
2220
2321# --- Internal helpers (POSIX-safe) -------------------------------------------
2422
2523_log_level_num () {
2624 # Map a level name to its numeric value.
2725 # Usage: _log_level_num "INFO"
2826 case " $1 " in
29- DEBUG) printf ' %s' 10 ;;
30- INFO) printf ' %s' 20 ;;
31- WARN) printf ' %s' 30 ;;
32- ERROR) printf ' %s' 40 ;;
33- * ) printf ' %s' 0 ;;
27+ DEBUG) printf ' %s' 10 ;;
28+ INFO) printf ' %s' 20 ;;
29+ WARN) printf ' %s' 30 ;;
30+ ERROR) printf ' %s' 40 ;;
31+ DIVIDER) printf ' %s' 20 ;;
32+ DIVIDER_END_OK) printf ' %s' 20 ;;
33+ DIVIDER_END_ERR) printf ' %s' 20 ;;
34+ * ) printf ' %s' 0 ;;
3435 esac
3536}
3637
@@ -42,9 +43,9 @@ _log_now() {
4243}
4344
4445_log_use_color () {
45- # Best-effort: use color only when LOG_COLOR=1, stderr/stdout are TTY, and not in CI/non‑TTY /dumb/no_color.
46+ # Best-effort: use color when stderr is TTY and not in CI/dumb/no_color.
4647 # `-t` is POSIX for test in many shells; if unsupported, this will just fail false-ish.
47- [ " ${LOG_COLOR} " = " 1 " ] && [ -t 1 ] && [ -z " ${NO_COLOR-} " ] && [ -n " ${TERM-} " ] && [ " ${TERM-} " != " dumb" ]
48+ [ -t 2 ] && [ -z " ${NO_COLOR-} " ] && [ -n " ${TERM-} " ] && [ " ${TERM-} " != " dumb" ]
4849}
4950
5051_log_fmt_prefix () {
@@ -85,11 +86,14 @@ _log_print() {
8586 # Colour (best-effort, stderr only)
8687 if _log_use_color; then
8788 case " $lvl " in
88- DEBUG) c_start=" $( printf ' 🐛 \033[36m' ) " ;; # cyan
89- INFO) c_start=" $( printf ' ℹ️ \033[32m' ) " ;; # green
90- WARN) c_start=" $( printf ' ⚠️ \033[33m' ) " ;; # yellow
91- ERROR) c_start=" $( printf ' 🛑 \033[31m' ) " ;; # red
92- * ) c_start=" " ;;
89+ DEBUG) c_start=" $( printf ' 🐛 \033[36m' ) " ;; # cyan
90+ INFO) c_start=" $( printf ' ℹ️ \033[32m' ) " ;; # green
91+ WARN) c_start=" $( printf ' ⚠️ \033[33m' ) " ;; # yellow
92+ ERROR) c_start=" $( printf ' 🛑 \033[31m' ) " ;; # red
93+ DIVIDER) c_start=" $( printf ' \033[32m' ) " ;; # green (⌛️ in title)
94+ DIVIDER_END_OK) c_start=" $( printf ' \033[32m' ) " ;; # green (✅ in title)
95+ DIVIDER_END_ERR) c_start=" $( printf ' \033[31m' ) " ;; # red (🛑 in title)
96+ * ) c_start=" " ;;
9397 esac
9498 c_end=" $( printf ' \033[0m' ) "
9599 else
@@ -99,22 +103,80 @@ _log_print() {
99103
100104 # Print to stderr (so logs don’t pollute stdout pipelines)
101105 # Shellcheck note: we intentionally want "$*" to preserve spaces as one message.
102- prefix=" $( _log_fmt_prefix " $lvl " ) "
106+ case " $lvl " in
107+ DIVIDER|DIVIDER_END_OK|DIVIDER_END_ERR) prefix=" " ;;
108+ * ) prefix=" $( _log_fmt_prefix " $lvl " ) " ;;
109+ esac
103110 # If message is empty, still print prefix.
104111 if [ " $# " -gt 0 ]; then
105- printf ' %s%s %s%s\n' " $c_start " " $prefix " " $* " " $c_end " >&2
112+ if [ -n " $prefix " ]; then
113+ printf ' %s%s %s%s\n' " $c_start " " $prefix " " $* " " $c_end " >&2
114+ else
115+ printf ' %s%s%s\n' " $c_start " " $* " " $c_end " >&2
116+ fi
106117 else
107118 printf ' %s%s%s\n' " $c_start " " $prefix " " $c_end " >&2
108119 fi
109120}
110121
122+ _log_terminal_width () {
123+ # Best-effort: COLUMNS, then tput cols, then stty size, else 80.
124+ w=" ${COLUMNS:- } "
125+ if [ -z " $w " ]; then
126+ w=" $( tput cols 2> /dev/null) " || w=" "
127+ fi
128+ if [ -z " $w " ]; then
129+ w=" $( stty size 2> /dev/null | awk ' {print $2}' ) " || w=" "
130+ fi
131+ case " $w " in
132+ ' ' |* [!0-9]* ) printf ' %s' 80 ;;
133+ * ) printf ' %s' " $w " ;;
134+ esac
135+ }
136+
137+ _log_divider () {
138+ # Arg 1: begin | end. Arg 2: script path. Arg 3 (end only): exit code (0 = success → green ✅, else red 🛑).
139+ kind=" $1 "
140+ path=" $2 "
141+ exit_code=" ${3:- 0} "
142+ case " $kind " in
143+ begin) title=" ⌛️ BEGIN $path " ; lvl=" DIVIDER" ;;
144+ end) if [ " $exit_code " = " 0" ]; then title=" ✅ END $path " ; lvl=" DIVIDER_END_OK" ; else title=" 🛑 END $path " ; lvl=" DIVIDER_END_ERR" ; fi ;;
145+ * ) title=" $path " ; lvl=" DIVIDER" ;;
146+ esac
147+ w=" $( _log_terminal_width) "
148+ len=$( printf ' %s' " $title " | wc -c | tr -d ' ' )
149+ left_n=$(( (w - len) / 2 ))
150+ right_n=$(( w - len - left_n ))
151+ [ " $left_n " -lt 0 ] && left_n=0
152+ [ " $right_n " -lt 0 ] && right_n=0
153+ left=" "
154+ i=0; while [ " $i " -lt " $left_n " ]; do left=" ${left} =" ; i=$(( i+ 1 )) ; done
155+ right=" "
156+ i=0; while [ " $i " -lt " $right_n " ]; do right=" ${right} =" ; i=$(( i+ 1 )) ; done
157+ _log_print " $lvl " " $left $title $right "
158+ }
159+
111160# --- Public API --------------------------------------------------------------
112161
113162log_debug () { _log_print DEBUG " $* " ; }
114163log_info () { _log_print INFO " $* " ; }
115164log_warn () { _log_print WARN " $* " ; }
116165log_error () { _log_print ERROR " $* " ; }
117166
167+ run () {
168+ # run "path/to/script"
169+ # Execute a script with pre-configured log (LOG_* exported; script runs in current shell and can use log_*).
170+ [ -z " $1 " ] && { log_error " run: missing script path" ; return 1; }
171+ [ ! -r " $1 " ] && { log_error " run: not readable or not found: $1 " ; return 1; }
172+ export LOG_LEVEL LOG_TIME LOG_PREFIX
173+ _log_divider begin " $1 "
174+ ( . " $1 " ) # Run in subshell
175+ ret=$?
176+ _log_divider end " $1 " " $ret "
177+ return $ret
178+ }
179+
118180die () {
119181 # die "message" [exit_code]
120182 # Returns non-zero if sourced; if executed in a subshell, exit is up to the caller.
@@ -123,19 +185,3 @@ die() {
123185 log_error " $msg "
124186 return " $code "
125187}
126-
127- # Convenience: assert command exists
128- require_cmd () {
129- # require_cmd curl git jq
130- # Returns non-zero (and logs) if any command is missing.
131- missing=" "
132- for cmd in " $@ " ; do
133- command -v " $cmd " > /dev/null 2>&1 || missing=" ${missing} ${cmd} "
134- done
135-
136- if [ -n " $missing " ]; then
137- log_error " missing required command(s):${missing} "
138- return 127
139- fi
140- return 0
141- }
0 commit comments