-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathfeeling.plugin.zsh
More file actions
executable file
Β·268 lines (240 loc) Β· 9.73 KB
/
Copy pathfeeling.plugin.zsh
File metadata and controls
executable file
Β·268 lines (240 loc) Β· 9.73 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
#!/bin/zsh
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:-β―}"
# Atomic write - write to temp file, then rename (atomic on POSIX)
_feeling_write() {
local tmp="$FEELING_DATA_PATH.tmp.$$"
printf '%s\n' "$@" > "$tmp" && \mv -f "$tmp" "$FEELING_DATA_PATH"
}
# Rotate backups (keep last 3)
_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() {
# Create feelings csv file if it doesn't exist
if ! [[ -f $FEELING_DATA_PATH ]]; then
mkdir -p "${FEELING_DATA_PATH%/*}" && touch "$FEELING_DATA_PATH"
echo "date,feeling" >>"$FEELING_DATA_PATH"
fi
# Cache file contents in array (with validation)
local -a _lines=()
local _line
while IFS= read -r _line || [[ -n $_line ]]; do
# Only keep valid lines: header or date,feeling format
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}
# Output feeling "calendar"
if [[ $# -eq 0 ]]; then
# Get data lines (skip header), last 28 entries
local -a data_lines=("${_lines[@]:1}")
local -a recent_entries=("${data_lines[@]: -28}")
# Get last date from data
local last_date=""
if [[ ${#data_lines} -gt 0 && -n "${data_lines[-1]}" ]]; then
last_date="${data_lines[-1]%%,*}"
fi
# Calculate start epoch: 27 days ago, find next Monday
local start_epoch=$((EPOCHSECONDS - 86400 * 27))
local dow
strftime -s dow '%w' $start_epoch
# dow: 0=Sun, 1=Mon, ..., 6=Sat - find days until Monday
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
# Skip entries before the calendar start
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
# Get current entry
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
# Choose color based on feeling
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
# Move to next entry
((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
# Add day to week output
week+=" ${color}${char}${no_color} "
# Print the week on Sunday
strftime -s dow '%w' $cur_epoch
if [[ $dow -eq 0 ]]; then
print "${week}\n"
week=""
fi
# Increment date
cur_epoch=$((cur_epoch + 86400))
done
# Print the last week if not empty
strftime -s dow '%w' $cur_epoch
if [[ $dow -ne 1 ]]; then
print "${week}\n"
fi
# Edit feelings data
else
if [[ $# -le 3 ]]; then
date=false
feeling=false
remove=false
while [[ $# -gt 0 ]]; do
# Get flags and arguments
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
# Validate date format
if ! [[ $date =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]]; then
echo "Invalid date: $date" >&2
return 1
fi
# Check date is not in the future
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))
# Get feeling
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
# Date defaults to today
if [[ $date = false ]]; then
strftime -s date '%Y-%m-%d' $EPOCHSECONDS
fi
# Backup feeling data (rotating backups)
_feeling_backup
if [[ $remove = true ]]; then
# Remove line matching date using array filtering
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
# Find which line to insert/update
if [[ $line_num -gt 1 ]]; then
# Iterate through data lines in reverse order
local -a data_lines=("${_lines[@]:1}")
local -a reversed=("${(Oa)data_lines[@]}")
local entry cur_date
for entry in "${reversed[@]}"; do
cur_date="${entry%%,*}"
# Found exact match, replace entire line
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
# Insert new entry after line_num
local -a new_lines=("${_lines[@]:0:$line_num}" "$date,$feeling" "${_lines[@]:$line_num}")
_feeling_write "${new_lines[@]}"
fi
}