zmodload zsh/datetime
FEELING_DATA_PATH="${FEELING_DATA_PATH:-$HOME/.config/feeling/feelings.csv}"
FEELING_FILLED_CHAR="${FEELING_FILLED_CHAR:-●}"
FEELING_EMPTY_CHAR="${FEELING_EMPTY_CHAR:-◯}"
_feeling_write() {
local tmp="$FEELING_DATA_PATH.tmp.$$"
printf '%s\n' "$@" > "$tmp" && \mv -f "$tmp" "$FEELING_DATA_PATH"
}
_feeling_backup() {
[[ -f "$FEELING_DATA_PATH" ]] || return 0
[[ -f "$FEELING_DATA_PATH.bak.2" ]] && \rm -f "$FEELING_DATA_PATH.bak.2"
[[ -f "$FEELING_DATA_PATH.bak.1" ]] && \mv -f "$FEELING_DATA_PATH.bak.1" "$FEELING_DATA_PATH.bak.2"
[[ -f "$FEELING_DATA_PATH.bak" ]] && \mv -f "$FEELING_DATA_PATH.bak" "$FEELING_DATA_PATH.bak.1"
\cp "$FEELING_DATA_PATH" "$FEELING_DATA_PATH.bak"
}
feeling() {
if ! [[ -f $FEELING_DATA_PATH ]]; then
mkdir -p "${FEELING_DATA_PATH%/*}" && touch "$FEELING_DATA_PATH"
echo "date,feeling" >>"$FEELING_DATA_PATH"
fi
local -a _lines=()
local _line
while IFS= read -r _line || [[ -n $_line ]]; do
if [[ $_line == "date,feeling" ]] || [[ $_line =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2},[0-9]+$ ]]; then
_lines+=("$_line")
fi
done < "$FEELING_DATA_PATH"
local line_num=${#_lines}
if [[ $# -eq 0 ]]; then
local -a data_lines=("${_lines[@]:1}")
local -a recent_entries=("${data_lines[@]: -28}")
local last_date=""
if [[ ${#data_lines} -gt 0 && -n "${data_lines[-1]}" ]]; then
last_date="${data_lines[-1]%%,*}"
fi
local start_epoch=$((EPOCHSECONDS - 86400 * 27))
local dow
strftime -s dow '%w' $start_epoch
local days_to_monday=$(( dow == 0 ? 1 : (dow == 1 ? 0 : 8 - dow) ))
start_epoch=$((start_epoch + 86400 * days_to_monday))
local tomorrow_epoch=$((EPOCHSECONDS + 86400))
local tomorrow
strftime -s tomorrow '%Y-%m-%d' $tomorrow_epoch
local no_color=$'\033[0m'
local week="\n"
local cur_epoch=$start_epoch
local cur_date entry_idx=1 entry_date entry_feeling
local color char
local start_date
strftime -s start_date '%Y-%m-%d' $start_epoch
while [[ $entry_idx -le ${#recent_entries} ]]; do
entry_date="${recent_entries[$entry_idx]%%,*}"
[[ $entry_date > $start_date || $entry_date == $start_date ]] && break
((entry_idx++))
done
if [[ $entry_idx -le ${#recent_entries} && -n "${recent_entries[$entry_idx]}" ]]; then
entry_date="${recent_entries[$entry_idx]%%,*}"
entry_feeling="${recent_entries[$entry_idx]#*,}"
else
entry_date=""
entry_feeling=""
fi
while true; do
strftime -s cur_date '%Y-%m-%d' $cur_epoch
[[ $cur_date == "$tomorrow" ]] && break
if [[ $cur_date == "$entry_date" ]]; then
char=$FEELING_FILLED_CHAR
if [[ "$entry_feeling" -ge 7 && "$entry_feeling" -le 10 ]]; then
color=$'\033[0;32m'
elif [[ "$entry_feeling" -ge 4 && "$entry_feeling" -le 6 ]]; then
color=$'\033[0;33m'
elif [[ "$entry_feeling" -ge 0 && "$entry_feeling" -le 3 ]]; then
color=$'\033[0;31m'
else
echo "Invalid feeling: $entry_feeling" >&2
return 1
fi
((entry_idx++))
if [[ $entry_idx -le ${#recent_entries} && -n "${recent_entries[$entry_idx]}" ]]; then
entry_date="${recent_entries[$entry_idx]%%,*}"
entry_feeling="${recent_entries[$entry_idx]#*,}"
else
entry_date=""
fi
else
color=$no_color
char=$FEELING_EMPTY_CHAR
fi
week+=" ${color}${char}${no_color} "
strftime -s dow '%w' $cur_epoch
if [[ $dow -eq 0 ]]; then
print "${week}\n"
week=""
fi
cur_epoch=$((cur_epoch + 86400))
done
strftime -s dow '%w' $cur_epoch
if [[ $dow -ne 1 ]]; then
print "${week}\n"
fi
else
if [[ $# -le 3 ]]; then
date=false
feeling=false
remove=false
while [[ $# -gt 0 ]]; do
while getopts "d:rh" opt; do
case $opt in
h)
echo
echo "Usage: $0 [options] <feeling>" >&2
echo
echo "Use script without arguments to see the current feelings"
echo
echo " -r Remove entry for specified date"
echo " -d Specify date, defaults to current date"
echo " -h Show help"
echo
echo " <feeling> How you felt on the date, must be from 1 to 10"
echo
return 0
;;
r)
if [[ $feeling != false ]]; then
echo "Invalid arguments: cannot use -r with $feeling" >&2
return 1
fi
remove=true
;;
d)
date=$OPTARG
if ! [[ $date =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]]; then
echo "Invalid date: $date" >&2
return 1
fi
local today
strftime -s today '%Y-%m-%d' $EPOCHSECONDS
if [[ $date > $today ]]; then
echo "Date is in the future: $date" >&2
return 1
fi
;;
\?)
echo "Invalid option: -$OPTARG" >&2
return 1
;;
esac
done
shift $((OPTIND - 1))
if [[ $# -gt 0 ]]; then
if [[ $1 -ge 1 && $1 -le 10 ]]; then
if [[ $feeling != false ]]; then
echo "Feeling set twice: $1" >&2
return 1
elif [[ $remove = true ]]; then
echo "Invalid arguments: cannot use -r with $1" >&2
return 1
else
feeling=$1
shift
fi
else
echo "Invalid feeling: $1" >&2
return 1
fi
fi
done
else
echo "Too many arguments: $*" >&2
return 1
fi
if [[ $date = false ]]; then
strftime -s date '%Y-%m-%d' $EPOCHSECONDS
fi
_feeling_backup
if [[ $remove = true ]]; then
local -a new_lines=("${_lines[1]}")
local -a data_entries=("${_lines[@]:1}")
local entry
for entry in "${data_entries[@]}"; do
[[ "${entry%%,*}" != "$date" ]] && new_lines+=("$entry")
done
_feeling_write "${new_lines[@]}"
return 0
fi
if [[ $line_num -gt 1 ]]; then
local -a data_lines=("${_lines[@]:1}")
local -a reversed=("${(Oa)data_lines[@]}")
local entry cur_date
for entry in "${reversed[@]}"; do
cur_date="${entry%%,*}"
if [[ $date == "$cur_date" ]]; then
echo "Date already has feeling: $date"
if read -rq "REPLY?Do you want to override? (y/n) "; then
_lines[$line_num]="$date,$feeling"
_feeling_write "${_lines[@]}"
fi
echo
return 0
elif [[ $date > $cur_date ]]; then
break
fi
line_num=$((line_num - 1))
done
fi
local -a new_lines=("${_lines[@]:0:$line_num}" "$date,$feeling" "${_lines[@]:$line_num}")
_feeling_write "${new_lines[@]}"
fi
}