#!/usr/bin/env bash # stim - simple timer # xmpp:morus@yourdata.forsale # mailto:morus@sdf.org # ------------------------------------------------------------------- # shopt -s extglob # Defaults: set timer to one minute. c=1 # Count to set. u=60 # Unit to use, in seconds. PROGNAME="${0##*/}" # Usage print_usage() { read -rd '' text << _EOF ${PROGNAME} - simple timer usage: ${PROGNAME} [options] [count] [message] description: stim will wait for desired duration, and then notify the user by flashing the terminal until it reads a keypress. options: -h : print this help message -c : count to use -q : disable time end notification -u {unit} : unit to use. sec/min/hour/(number of seconds) -e {string} : evaluate provided command string when time runs out examples: set timer to one minute, notify after: stim # it's the default behaviour set timer to 2 hours, display "call sean" after: stim -u h 2 call sean set timer to 30 seconds, don't notify but play testospin after: stim -q -u sec -e "cmus-remote -f ~/testospin.ogg" 30 same as above, but using different syntax and the && shell operand: stim -qu 1 30 && cmus-remote -f ~/testospin.ogg notes: With some caveats, stim can be backgrounded with standard shell features like the & operand or the SIGTSTP signal commonly bound to Ctrl+Z. If you're stuck with a flashing screen and pressing keys doesn't stop it, you're likely looking to foreground the stim job using the `fg` shell command. _EOF echo "${text}" } # Parse options. Takes $@, returns a number. # Do shift $number and the $@ becomes just the arguments. parse_options() { while getopts 'hqu:e:c:' opt do case "${opt}" in h) print_usage exit ;; c) if is_number "${OPTARG}" then c="${OPTARG}" ; skiparg[c]=1 else echo "${PROGNAME} options error: ${OPTARG} is not a number" >&2 exit 31 fi ;; u) case "${OPTARG}" in s*) u=1 ;; m*) u=60 ;; h*) u=3600 ;; +([0-9])) u="${OPTARG}";; *) print_usage >&2 ; exit 2 ;; esac ;; e) e_string="${OPTARG}" ;; q) quiet=1 ;; *) echo "internal error" >&2 exit 3 ;; esac done return $(( OPTIND - 1 )) } # Helpers is_number() { local j for j do [[ "${j}" =~ ^[0-9]+$ ]] || return 1 done unset j } # Invoked when time runs out. notify() { echo "${1:-${PROGNAME}: ${s}s has passed, timer ends}" until read -rn 1 -t 0.5 do tput flash; echo -ne "\a"; sleep 0.1 tput flash; echo -ne "\a" done return 0 } # Takes count and units as arguments # Prints remaining time every passing unit wait_count() { is_number "${@}" || exit 4 local i=${1} until (( --i < 0 )) do sleep ${2} (( quiet )) || echo $(( i*u )) done unset i } # Main runtime main() { parse_options "${@}" shift ${?} # If a number is provided, set the count. if [[ ${1} =~ ^[0-9]+$ ]] && ! (( skiparg[c] )) then c=${1} shift fi # DEBUG / Some feedback echo "Unit $u Count $c" >&2 echo "Timer set to $(( c*u )) seconds" >&2 # Time to... time if wait_count ${c} ${u} then (( e_string )) && eval "${e_string}" (( quiet )) || notify "${*}" else echo "${PROGNAME}: I can't sleep" >&2 exit 1 fi } # ------------------------------------------------------------------- # main "${@}" ### Wishlist of features: # exec something on time end ( DONE ) # quiet mode ( DONE ) # # pausing - do this with trapping signals or something # relevant: # - help jobs # - help [fb]g # - stty -a # - trap # - ^Z doesn't really work now for pausing but should if we # `sleep 1` 60 times instead of `sleep 60` once. It would also # allow for a countdown display. # # backgrounding - on the other hand, ^Z is perfect for backgrounding # with the `sleep 60` implementation, except it won't let the notify # fire. # # more on sleep 60 vs sleep 1 implementation details: # - several sleeps give more meaning to unit and count definitions