#!/usr/bin/env zsh # To use it, just place it in your $fpath # say # To use this, either source it from your script, or place it in your $fpath # and autoload it. Then, use the `say`-function. # RFC 5424 # https://www.rfc-editor.org/rfc/rfc5424 # # Numerical Severity # Code # # 0 Emergency: system is unusable # 1 Alert: action must be taken immediately # 2 Critical: critical conditions # 3 Error: error conditions # 4 Warning: warning conditions # 5 Notice: normal but significant condition # 6 Informational: informational messages # 7 Debug: debug-level messages # # # Variables # # Standardized ${0:t} handling, according to the Zsh Plugin Standard # https://zplugin.readthedocs.io/en/latest/zsh-plugin-standard/ 0="${${ZERO:-${0:#${ZSH_ARGZERO:-}}}:-${(%):-%N}}" 0="${${(M)0:#/*}:-$PWD/$0}" # Default severity for messages typeset -gi _SAY_DEFAULT_SEV=${_SAY_DEFAULT_SEV:-6} # Print severity typeset -gi _SAY_PRINT_SEV=${_SAY_PRINT_SEV:-0} # Print date typeset -gi _SAY_PRINT_DATE=${_SAY_PRINT_DATES:-0} # Date format. typeset -g _SAY_DATE_FMT=${_SAY_DATE_FMT:-'%Y-%m-%dT%T %Z'} # 0: no color # 1: only color severity # 2: color severity and date # 3: color everything typeset -gi _SAY_COLOR=${_SAY_COLOR:-3} # Tags for log-messages typeset -gA _SAY_SEV_MSG _SAY_SEV_MSG=( 0 ${_SAY_SEV_MSG_0-'[EMERGENCY]'} 1 ${_SAY_SEV_MSG_1-'[ALERT]'} 2 ${_SAY_SEV_MSG_2-'[CRITICAL]'} 3 ${_SAY_SEV_MSG_3-'[ERROR]'} 4 ${_SAY_SEV_MSG_4-'[WARNING]'} 5 ${_SAY_SEV_MSG_5-'[NOTICE]'} 6 ${_SAY_SEV_MSG_6-'[INFO]'} 7 ${_SAY_SEV_MSG_7-'[DEBUG]'} ) # I really want to stick to known colors that work in most terminals, but I # also really want grey debug-messages. So let's check if the terminal supports # 256 colors before we use them. # https://zsh.sourceforge.io/Doc/Release/Zsh-Modules.html#The-zsh_002ftermcap-Module zmodload -F zsh/termcap +p:termcap 2>/dev/null && \ [[ ${termcap[Co]:-0} == 256 ]] && _SAY_COLORCODE_s7='%F{008}' _SAY_COLORCODE_e7='%f' # Set colorcodes typeset -gA _SAY_COLORCODE _SAY_COLORCODE=( s0 ${_SAY_COLORCODE_s0-'%B%K{white}%F{red}'} e0 ${_SAY_COLORCODE_e0-'%f%k%b'} s1 ${_SAY_COLORCODE_s1-'%B%F{red}'} e1 ${_SAY_COLORCODE_e1-'%f%b'} s2 ${_SAY_COLORCODE_s2-'%B%F{red}'} e2 ${_SAY_COLORCODE_e2-'%f%b'} s3 ${_SAY_COLORCODE_s3-'%F{red}'} e3 ${_SAY_COLORCODE_e3-'%f'} s4 ${_SAY_COLORCODE_s4-'%F{yellow}'} e4 ${_SAY_COLORCODE_e4-'%f'} s5 ${_SAY_COLORCODE_s5-'%F{cyan}'} e5 ${_SAY_COLORCODE_e5-'%f'} s6 ${_SAY_COLORCODE_s6-''} e6 ${_SAY_COLORCODE_e6-''} s7 ${_SAY_COLORCODE_s7-''} e7 ${_SAY_COLORCODE_e7-''} ) # Helptext typeset -gr _SAY_HELPSTRING='NAME say - a function you can use to log messages in a zsh-script. SYNOPSIS say [OPTIONS] MSG Alternative syntax: say [OPTIONS] SEVERITY "MSG" [EXITCODE] This allows you to use \`say debug this is a debug msg\`. But, if you set severity in an option as well, it will override the severity in the message-part. I.e. \`say -4 debug this is a debug msg\` will print as an ERROR, not a DEBUG. Exitcode set as option also overrides an exitcode at the end of the line. Also, if multiple severity-options are given, i.e. \`-2 -4\` or \`-54\`, the last one will take precedence. DESCTIPTION say has the following options: -0 Log with severity EMERGENCY -1 Log with severity ALERT -2 Log with severity CRITICAL -3 Log with severity ERROR -4 Log with severity WARNING -5 Log with severity NOTICE -6 Log with severity INFO -7 Log with severity DEBUG -C No color. This is the default when run from a non-interactive shell (actually, when $TTY is unset) -c NUM NUM can be 0, 1, 2, or 3. They each mean the following: 0: no color 1: only color severity-tag 2: color severity-tag and date 3: color everything (default) Colors are ignored if in a non-interactive shell (when $TTY is unset) -D Do not print date and time (default) -d Print date and time -e NUM Exit, with NUM as exitcode, after logging message. NUM must be in the range {0..255}. -h Print this help-text -n No color, no date, no severity-tag. Same as \`-C -D -S\`. -r NUM return with NUM as return-code. NUM must be in the range {0..255}. \`-e\` (exit) takes precedence over \`-r\` (return). -S Do not print severity-tag (default) -s Print the severity-tag, for example "[DEBUG] message" -u NUM Print to this filedescriptor. 1 = stdout, 2 = stderr. By default, LOGLEVEL 0-4 prints to stderr, and 5-7 prints to stdout. AUTHOR Dennis Eriksen ' zmodload -F zsh/datetime +b:strftime 2>/dev/null || true # Just use `date` if we can't load datetime # say [OPTIONS] MSG say() { emulate -LR zsh unsetopt unset # Make sure LOGLEVEL is an integer # (L) is an expansion-flag that converts letters to lower case # Do this inside the function because LOGLEVEL can easily be set and user by something else. integer loglevel case ${(L)LOGLEVEL:-6} in 0|emerg*) loglevel=0 ;; 1|alert) loglevel=1 ;; 2|crit*) loglevel=2 ;; 3|err*) loglevel=3 ;; 4|warn*) loglevel=4 ;; 5|notice) loglevel=5 ;; 6|info*) loglevel=6 ;; 7|debug) loglevel=7 ;; *) loglevel=6 ;; esac; local sev msg msgsev msgdate string printparam printfd integer exitbool exitcode returnbool returncode # # This is where the magic happens # # handle options local OPTIND OPTARG opt while getopts ":01234567Cc:Dde:hnr:Ssu:" opt; do case $opt in 0) sev=0 ;; 1) sev=1 ;; 2) sev=2 ;; 3) sev=3 ;; 4) sev=4 ;; 5) sev=5 ;; 6) sev=6 ;; 7) sev=7 ;; C) _SAY_COLOR=0 ;; c) [[ ! $OPTARG == <0-3> ]] && print -u2 -- "\`${0:t} -c\` takes exactly one argument - an int between 0 and 3. You provided: \'$OPTARG\'" && return 100 _SAY_COLOR=$OPTARG ;; D) _SAY_PRINT_DATE=0 ;; d) _SAY_PRINT_DATE=1 ;; e) [[ ! $OPTARG == <0-255> ]] && print -u2 -- "\`${0:t} -e\` takes exactly one argument - an int between 0 and 255. You provided: \'$OPTARG\'" && return 103 exitcode=$OPTARG exitbool=1 ;; h) print -- $_SAY_HELPSTRING && return 0 ;; n) _SAY_COLOR=0 _SAY_PRINT_DATE=0 _SAY_PRINT_SEV=0 ;; r) [[ ! $OPTARG == <0-255> ]] && print -u2 -- "\`${0:t} -r\` takes exactly one argument - an int between 0 and 255. You provided: \'$OPTARG\'" && return 105 returncode=$OPTARG returnbool=1 ;; S) _SAY_PRINT_SEV=0 ;; s) _SAY_PRINT_SEV=1 ;; u) [[ ! $OPTARG == <0-> ]] && print -u2 -- "\`${0:t} -u\` takes one argument - a positive integer. \`${0:t} -u n\` will print to filedescriptor n." && return 104 printfd=$OPTARG ;; :) print -u2 -- "${0:t}: option \'$OPTARG\' is missing an argument" print -u2 -- "Try \'${0:t} -h\' for more info" && return 101 ;; ?) print -u2 -- "${0:t}: invalid option -- \'$OPTARG\'" print -u2 -- "Try \'${0:t} -h\' for more info" && return 102 ;; esac done; (( OPTIND > 1 )) && shift $(( OPTIND -1 )) # If not set by option, try to determine severity from $1, but only if there # is more than one argument remaining, and there are no spaces in $1. # This enables us to use `say error "MESSAGE"`, but won't catch `say "error message"` if [[ -z $sev && $ARGC -gt 1 && $1 != *' '* ]]; then case ${(L)1} in 0|emerg*) sev=0 ;; 1|alert*) sev=1 ;; 2|crit*) sev=2 ;; 3|err*) sev=3 ;; 4|warn*) sev=4 ;; 5|notice*) sev=5 ;; 6|info*) sev=6 ;; 7|debug*) sev=7 ;; esac; # Do the same thing with exitcode if (( ! exitbool && ARGC == 3 )) && [[ $3 == <0-255> ]]; then exitcode=$3 exitbool=1 shift -p # Remove the last positional argument ($3), as it was just used fi [[ $sev == <0-7> ]] && shift 1 # Shift arguments if we managed to set sev fi; # Set sev if we failed to set it aleady. Also, force integer. integer sev=${sev:-_SAY_DEFAULT_SEV} # Only continue of severity is below loglevel (( sev <= loglevel )) || return 0 # If no arguments, just add newline and return (( ARGC )) || { print -- && return } # We have shifted enough that the rest of the vars should be the message msg=$@ [[ -n $TTY ]] || _SAY_COLOR=0 # No color unless we have a TTY to print to (( _SAY_COLOR )) && printparam='-P' # We don't need to use -P unless we have colors. if (( _SAY_PRINT_DATE )); then (( $+builtins[strftime] )) \ && msgdate="$(strftime "$_SAY_DATE_FMT")" \ || msgdate="$(date "+$_SAY_DATE_FMT")" (( _SAY_COLOR >= 2 )) && msgdate="$_SAY_COLORCODE[s$sev]${msgdate}$_SAY_COLORCODE[e$sev]" string="$msgdate " fi if (( _SAY_PRINT_SEV )); then msgsev="$_SAY_SEV_MSG[$sev]" (( _SAY_COLOR >= 1 )) && msgsev="$_SAY_COLORCODE[s$sev]${msgsev}$_SAY_COLORCODE[e$sev]" string+="$msgsev " fi (( _SAY_COLOR == 3 )) && msg="$_SAY_COLORCODE[s$sev]${msg}$_SAY_COLORCODE[e$sev]" string+="$msg" # Set filedescriptor to print to (( ! printfd && 4 >= sev )) && printfd=2 # stderr (( ! printfd && sev >= 5 )) && printfd=1 # stdout print $printparam -u$printfd -- "$string" (( ! exitbool )) || exit $exitcode (( ! returnbool )) || return $returncode return 0 } say debug "function \`${0:t}\` loaded"