250 lines
5.2 KiB
Bash
250 lines
5.2 KiB
Bash
function log-hours {
|
|
case $1 in
|
|
on)
|
|
shift
|
|
if [ $# -eq 0 ]
|
|
then
|
|
LAST_MESSAGE="$(grep 'BEGIN' \
|
|
"${HOME}/.log-hours" |
|
|
tail -1 |
|
|
cut -f 3-)"
|
|
echo -e "Reporting beginning of work with" \
|
|
"message\n\t${LAST_MESSAGE}"
|
|
log-hours_on "${LAST_MESSAGE}"
|
|
else
|
|
log-hours_on $*
|
|
fi
|
|
;;
|
|
off)
|
|
shift
|
|
log-hours_off $*
|
|
;;
|
|
total)
|
|
shift
|
|
if [ $# -gt 0 ]
|
|
then
|
|
log-hours_total $*
|
|
else
|
|
log-hours_total $(date -I)
|
|
fi
|
|
;;
|
|
today)
|
|
TODAY="$(date -I)"
|
|
log-hours_total "${TODAY}"
|
|
;;
|
|
yesterday)
|
|
YESTERDAY="$(grep -v "$(date -I)" \
|
|
"${HOME}/.log-hours" |
|
|
tail -1 |
|
|
awk '{print $2}' |
|
|
cut -d'T' -f1)"
|
|
log-hours_total "${YESTERDAY}"
|
|
;;
|
|
edit)
|
|
"${EDITOR}" "${HOME}/.log-hours"
|
|
;;
|
|
*)
|
|
echo '[ERROR] '"'$1'"' is not a valid command.' >&2
|
|
return 1
|
|
;;
|
|
esac
|
|
return 0
|
|
}
|
|
|
|
function log-hours_on {
|
|
echo -e "BEGIN\t$(date -Imin)\t$*" >> "${HOME}/.log-hours"
|
|
}
|
|
|
|
function log-hours_off {
|
|
echo -e "END\t$(date -Imin)\t$*" >> "${HOME}/.log-hours"
|
|
}
|
|
|
|
function log-hours_total {
|
|
if DATE=$(date -I -d "$*" 2>/dev/null)
|
|
then
|
|
printf "Date:\t${DATE}\n"
|
|
grep "${DATE}" "${HOME}/.log-hours" |
|
|
awk -f <(cat <<-'EOF'
|
|
BEGIN {
|
|
begin = 0
|
|
i = 0
|
|
cut_cmd = "cut -f3-"
|
|
}
|
|
/^\<BEGIN\>/ {
|
|
to_epoch = ("date -d \"" $2 "\" +\"%s\"")
|
|
if (begin == 0) {
|
|
to_epoch | getline begin
|
|
print $0 |& cut_cmd
|
|
close(cut_cmd, "to")
|
|
cut_cmd |& getline task_title
|
|
task_titles[i] = task_title
|
|
}
|
|
close(to_epoch)
|
|
close(cut_cmd)
|
|
}
|
|
|
|
/^\<END\>/ {
|
|
to_epoch = ("date -d \"" $2 "\" +\"%s\"")
|
|
if (begin != 0) {
|
|
to_epoch | getline end
|
|
total += (end - begin)
|
|
task_hours[i] = (end - begin) / 3600.0
|
|
sum_rounded += round(task_hours[i], 0.25)
|
|
i++;
|
|
begin = 0
|
|
}
|
|
else {
|
|
print "ERROR: an END is found at", $2,
|
|
"but it is doesn't have an",
|
|
"equivalent BEGIN."
|
|
begin = 0
|
|
}
|
|
close(to_epoch)
|
|
}
|
|
|
|
END {
|
|
if (FNR == 0) {
|
|
print "WARNING: zero lines to process."
|
|
}
|
|
absolute = total / 3600.0
|
|
printf "Hours (absolute):\t%.2f\n", absolute
|
|
rounded = round(absolute, 0.25)
|
|
printf "Hours (rounded sum):\t%.2f\n", rounded
|
|
printf "Hours (sum of rounded):\t%.2f\n", sum_rounded
|
|
for (j = 0; j < i; j++) {
|
|
printf "\t%.2f\t%.2f\t%s\n", task_hours[j], round(task_hours[j], 0.25), task_titles[j]
|
|
#printf "\t%.2f\t%s\n", task_hours[j], task_titles[j]
|
|
}
|
|
}
|
|
|
|
function abs(value) {
|
|
if (value < 0) {
|
|
return (-1 * value);
|
|
}
|
|
return value;
|
|
}
|
|
|
|
function sgn(x) {
|
|
return (x < 0)? (-1) : 1;
|
|
}
|
|
|
|
function ceil(x) {
|
|
return (x == int(x)) ? x : int(x + 1)
|
|
}
|
|
|
|
function floor(x) {
|
|
return int(x)
|
|
}
|
|
|
|
function round(value, precision, value_to_round) {
|
|
# Unified the round (half [1]) away from zero [2] with the
|
|
# rounding to a specified multiple [3].
|
|
#
|
|
# [1]: https://en.wikipedia.org/wiki/Rounding#Round_half_away_from_zero
|
|
# [2]: https://en.wikipedia.org/wiki/Rounding#Round_away_from_zero
|
|
# [3]: https://en.wikipedia.org/wiki/Rounding#Rounding_to_a_specified_multiple
|
|
if (precision == 0) {
|
|
precision = 1.0
|
|
}
|
|
else {
|
|
value_to_round = value / (precision * 1.0)
|
|
}
|
|
# round half away from zero [1]
|
|
#return (sgn(value) * floor(abs(value_to_round) + 0.5) * precision)
|
|
# round away from zero [2]
|
|
return (sgn(value) * ceil(abs(value_to_round)) * precision)
|
|
}
|
|
EOF
|
|
)
|
|
else
|
|
echo "ERROR: '$*' is not a valid date. Try writing it" \
|
|
"according to the ISO 8601 format."
|
|
fi
|
|
}
|
|
|
|
function log-hours_complete {
|
|
if [ $3 == "on" ]
|
|
then
|
|
COMPREPLY=$(grep 'BEGIN' ~/.log-hours |
|
|
cut -f3- |
|
|
sort |
|
|
uniq -c |
|
|
sed 's/^\s*//' |
|
|
sort -t' ' -k1nr -k2 |
|
|
cut -d' ' -f2- |
|
|
fzy)
|
|
fi
|
|
}
|
|
|
|
# Bash completion for `log-hours on`
|
|
complete -o nospace -F log-hours_complete log-hours
|
|
|
|
function weeks-task {
|
|
seq $(( "$(date +'%u')" - 1 )) -1 0 |
|
|
while read ENTRY
|
|
do
|
|
egrep $'BEGIN\t'"$(date -I --date="$ENTRY days ago")" ~/.log-hours # filters only this week's entries
|
|
done |
|
|
cut -f 3- | # take just the logged entries
|
|
sed -e 's/^[[:blank:]]*//' -e 's/[[:blank:]]*$//' | # trimming the entries
|
|
awk 'entry[$0]++ == 0' # print only the first appearance of the entry
|
|
}
|
|
|
|
function weeks-hours {
|
|
MATCH_WORD="absolute"
|
|
|
|
if [ -n "$1" ]
|
|
then
|
|
case $1 in
|
|
absolute)
|
|
MATCH_WORD="absolute"
|
|
;;
|
|
rounded)
|
|
MATCH_WORD="rounded sum"
|
|
;;
|
|
sum-rounded)
|
|
MATCH_WORD="sum of rounded"
|
|
;;
|
|
*)
|
|
echo '[ERROR] '"'$1'"' is not a valid' \
|
|
'command.' >&2
|
|
return 1
|
|
;;
|
|
esac
|
|
fi
|
|
|
|
seq $(( "$(date +'%u')" - 1 )) -1 0 |
|
|
while read ENTRY; do
|
|
log-hours total "$(date -I --date="$ENTRY days ago")"
|
|
done |
|
|
sed '/'"${MATCH_WORD}"'/{s/^.*\t//;p};/./d' |
|
|
tr '\n' '+' |
|
|
sed 's/+$/\n/' |
|
|
bc
|
|
}
|
|
|
|
function redmine {
|
|
COMMAND=${*:-yesterday}
|
|
CONTENT=$(log-hours ${COMMAND})
|
|
|
|
# Print header.
|
|
echo "${CONTENT}" |
|
|
sed -ne '/^[^[:blank:]]/{p}'
|
|
|
|
# Unify hours per task.
|
|
echo "${CONTENT}" |
|
|
egrep '^[[:blank:]]' |
|
|
awk -F '\t' '
|
|
{
|
|
precise[$4] = precise[$4] + $2
|
|
rounded[$4] = rounded[$4] + $3
|
|
}
|
|
|
|
END {
|
|
for (t in precise) {
|
|
printf("\t%0.2f\t%0.2f\t%s\n", precise[t], rounded[t], t)
|
|
}
|
|
}' |
|
|
sort -t $'\t' -k4
|
|
}
|