-
Notifications
You must be signed in to change notification settings - Fork 1
/
habit
executable file
·308 lines (274 loc) · 9.49 KB
/
habit
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
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
#!/bin/bash
#
# habit Script to mark actions
#
# Author: Grant Bowman <grantbow@grantbow.com>
#
# Version: @(#)goodmorning 1.2 2004-04-03 grantbow@grantbow.com
# habitlog 2.0 2006-02-28 grantbow@grantbow.com
# habit 3.0 2010-07-12 grantbow@grantbow.com
# habit 3.1 2010-07-25 grantbow@grantbow.com
# habit 3.2 2010-07-31 grantbow@grantbow.com
# habit 3.3 2011-12-27 grantbow@grantbow.com
#
# Dependencies: iselect, dc, coreutils
# Recommends Dependencies: gnuplot
#
# Config Spec: each line (no blanks please) in habit.conf is an action name such as
# action1
# Script Spec: commands are sourced and run for each action like .habit.action1
# echo "action1!"
#
# Display: Software exists for making better graphs of this data.
# Gnumeric Spreadsheet can change epoch seconds to dates using =unix2date()
# OpenOffice.org Calc can import the data as csv and use tabs as separators
# xfig use is possible
# ImageMagick use is possible
# rlplot looks possible
# xplot looks possible
#
# History: originally a script for playing music, marking wake and sleep times
#
set -e
NAME="habit"
DESC="script for marking actions and running scripts"
# depends on $HOME
LOG=$HOME/.$NAME.log
CONFIGFILE=$HOME/.$NAME.conf
CHARTFILE=$HOME/.$NAME.png
SECONDS=`date +%s` # current
if [[ $1 == "--help" ]] ; then {
echo "Usage: $NAME {action name|action number|--help}"
echo " $NAME (without options is interactive using iselect)"
echo ""
echo "Input files: $CONFIGFILE"
echo " $LOG"
echo " $HOME/.$NAME.{your action names}"
echo "Output file: $CHARTFILE"
echo ""
echo "$NAME marks actions."
echo ""
echo -e "\tFor further information see http://activehabits.com"
exit 0
}
fi
########### * assumes * existence of tail, cut, date from Debian coreutils package
if ! [[ -x `which dc` ]] ; then {
echo "Please install dc.";
exit 1;
}
fi
if ! [[ -x `which iselect` ]] ; then {
echo "Please install iselect.";
exit 1;
}
fi
# hot keys easy to implement outside the script with numeric or action name parameter
if ! [ -e $LOG ]; then { # missing log
touch $LOG ;
echo -e "#action_Name\tepoch_seconds\thour_of_day\tuser_input\tinterval_hours\treadable_date\tlocation" >> $LOG
LASTSECONDS=$SECONDS
LASTACTION="NONE"
LASTDATE="NEVER" # only used for display of iselect
#LASTDATE=`date -d "1970-01-01 UTC $LASTSECONDS seconds" `
}
else { # log exists, read last action
LASTSECONDS=`tail -1 $LOG | cut -f 2`
LASTACTION=`tail -1 $LOG | cut -f 1`
# future, could check for last action of each type, or over a week or month
# read "some" action history
# tail -(cycle length*4) | $LASTaction
if [[ $LASTSECONDS == "epoch seconds" ]]; then { # no data in the log yet
LASTSECONDS=$SECONDS
LASTACTION="NONE"
LASTDATE="NEVER"
}
else { # assume log not empty, LASTSECONDS and LASTACTION are ok
LASTDATE=`date -d "1970-01-01 UTC $LASTSECONDS seconds" `
}
fi
}
fi
# Read a config file if it is present.
if [ -r $CONFIGFILE ] ; then {
readarray -t CYCLE < $CONFIGFILE # a bash builtin, CYCLE is an indexed array
}
else {
# elegant way to declare CYCLE and write the file
CYCLE=(wake sleep)
printf '%s\n' ${CYCLE[*]} >> $CONFIGFILE
}
fi
# check CYCLE values for blank lines
# change loop is omitted indexes won't cause problems
# for each?
#Y=${#CYCLE[*]}
#for (( X=0 ; $X < $Y ; X++ )) ; do
# if [ -n "${CYCLE[X]}" ] ; then
# unset CYCLE[X]
# fi
#done
# TO DO & future ideas
#
# l10n (localization) strings
#
# pid file lock so multiple terminals can't use this app's iselect at the same time
#echo -n "$CURRENTPROCESSPID" > $PIDFILE
#
# catch skipped actions in the cycle
#prompt for missed time value for repeating actions
#
# graphical summaries
#option --week to give some kind of search results on the data collected for the past week
#maybe --month as well.
#
MENUCONTENT=
INITSELECTION="1" # pre-highlight item, default is first item
Y=${#CYCLE[*]} # set because there's trouble evaluating directly in the for (())
# when change loop is omitted indexes won't cause problems # for each?
for (( X=0 ; $X < $Y ; X++ )) ; do {
C=${CYCLE[X]} # current action name
# construct menu content for iselect
Z=$(($X+1)) # +1 convert from zero index
MENUCONTENT="$MENUCONTENT<s:$Z>$Z.$C "; # if " " instead of "." 2 params
#echo "${CYCLE[X]}" # debug
#echo "$LASTACTION" # debug
if [[ $LASTACTION = ${CYCLE[X]} ]] ; then { # highlight the next action
INITSELECTION=$(( $Z + 1 )) # +1 next value
}
fi
if [[ $1 == $C || $1 == $Z ]] ; then { # check for numeric or named action on command line
MENURETURN="$Z" # valid parameter passed to script, no need for menu now
}
fi
}
done
# if INITSELECTION is too high, highlight first action
if [[ $INITSELECTION -gt $Y ]] ; then {
INITSELECTION="1"
}
fi
if [[ $1 && ! $MENURETURN ]] ; then { # unrecognized value passed to script
echo "unrecognized: $1"
echo "Usage: $NAME {action name|action number|--help}"
exit 1
}
fi
# present menu with configuration, chart & previous action
#echo $INITSELECTION
#sleep 10
Z=$(( ${#CYCLE[*]} + 1 )) # configuration menu item, Z+1 charts actions
if [[ ! $MENURETURN ]] ; then {
MENURETURN="`iselect $MENUCONTENT \"\" \"<s:$Z>$(($Z)).configuration\" \"<s:$Z+1>$(($Z+1)).chart actions\" \"<s:$Z+2>$(($Z+2)).cancel\" \"\" \"\" \"Previous action - $LASTACTION $LASTDATE\" -p $INITSELECTION -n \"\" -kj:KEY_DOWN -kk:KEY_UP -kl:KEY_RIGHT -kSPACE:RETURN -t \"$NAME testing version\"`"
# couldn't get ESC to map to q or KEY_LEFT - quite annoying
}
fi
#"<s:1>1 morning" " song1" " song2" " {email assistant}" "<s:a1> Add Trigger" "<s:2>2 evening" " song3" " song4" " sing5" "<s:a2> Add Trigger" "" "" "Previous action - `date -d "1970-01-01 UTC 1132835026 seconds"`" -Q "1" -n "" -t "lullibuy (alpha)"
# write a message to STDOUT after each run
#echo -e "\tCheck out http://activehabits.com"
SELECTINDEX=$(($MENURETURN-1)) # back to 0 based array
Z=$(( $Z - 1 )) # back to 0 based
if [[ $SELECTINDEX == $(($Z)) ]] ; then { # configuration selected
echo -e "\nActions are in: $CONFIGFILE (exists)\n"
echo -e "Marked actions: $LOG (exists)\n"
if [ -f $CHARTFILE ] ; then
echo -e "Marked action chart: $CHARTFILE (exists)\n"
else
echo -e "Marked action chart: run $NAME and select chart actions\n"
fi
echo -e "\nCommands in these files are sourced when their action is marked:\n"
# there must be a more elegant way to do this
for (( X=0 ; $X < $Y ; X++ )) ; do {
A="$HOME/.$NAME.${CYCLE[X]}"
if [ -f $A ] ; then
printf ' %s\n' "$A (exists)"
else
printf ' %s\n' "$A"
fi
}
done
echo -e
echo -e "\t$NAME --help for more info"
}
elif [[ $SELECTINDEX == $(($Z+1)) ]] ; then { # chart actions
if ! [[ -x `which gnuplot` ]] ; then {
echo "Please install gnuplot.";
exit 1;
}
fi
gnuplot - << EOF
set title 'Marked Actions'
set grid
set border 6
set size ratio 0.5
unset key
set x2data time
set timefmt "%s"
set format x2 "%a, %b %d"
set x2tics 86400
unset x2mtics
unset xlabel
unset xdata
unset xtics
unset mxtics
set ylabel 'hour of day'
set yrange [24:0]
set ytics (0,6,12,18,24)
set pointsize 4
plot "$LOG" using 2:( valid(3) ? \$3 : \$4 ) axis x1y1 with points 11
pause 5 "saving copy of chart to $CHARTFILE"
set terminal png
set output "$CHARTFILE"
replot
printf "Any key or button will terminate"
pause mouse any
EOF
}
elif [[ $SELECTINDEX -le ${#CYCLE} ]] ; then {
# log the action
MYHOUR=`date +%k`
XMIN=`date +%M`
MYMIN=`dc -e " 2 k $XMIN 60 / n "`
if [[ $MYMIN == 0 ]] ; then
MYMIN = ".0" # correction for possible dc output
fi
# #action_Name\t
# epoch_seconds\t
# hour_of_day\t
# user_input\t
# interval_hours\t
# readable_date\t
# location
if [[ $LASTSECONDS == $SECONDS ]] ; then
echo -e "${CYCLE[SELECTINDEX]}\t$SECONDS\t$MYHOUR$MYMIN\t\t0.0\t`date`" >> $LOG
else
echo -e "${CYCLE[SELECTINDEX]}\t$SECONDS\t$MYHOUR$MYMIN\t\t`dc -e \" 2 k $SECONDS $LASTSECONDS - 3600 / n\"`\t`date`" >> $LOG
fi
# use dc to give the difference between two second values (replace variables):
#
# dc -e " 2 k $SECONDS $LASTSECONDS - 3600 / n"
#
# use this to give the seconds of any --date (replace string):
#
# date --date="Jun 28 19:38:15 PDT 2003" +%s
#
# use this to convert epoch seconds back using local time zone (replace 946684800):
#
# date -d '1970-01-01 UTC 946684800 seconds' +"%Y-%m-%d T %T%z"
#
# use this to create an hour of day value
#
# dc -e " 2 k `date +\"%M\"` 60 / n "
#
##### source (execute) commands from $TRIGGERFILE
TRIGGERFILE=$HOME/.$NAME.${CYCLE[SELECTINDEX]}
if [ -r $TRIGGERFILE ] ; then
. $TRIGGERFILE
fi
}
else { # iselect shouldn't allow this
echo -e "Nothing marked in .$NAME.log."
}
fi
##### conclude
exit 0