#!/usr/bin/env bash
# stim - simple timer
# xmpp:
[email protected]
# mailto:
[email protected]
# ------------------------------------------------------------------- #
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