2026-07-09 23:08:42 -04:00
|
|
|
#!/usr/bin/env bash
|
|
|
|
|
set -euo pipefail
|
|
|
|
|
|
|
|
|
|
HABITS_FILE="${HABITS_FILE:-$HOME/.local/share/os/habits.md}"
|
|
|
|
|
|
|
|
|
|
usage() {
|
|
|
|
|
cat <<'EOF'
|
|
|
|
|
Usage: habits <command>
|
|
|
|
|
|
|
|
|
|
Commands:
|
2026-07-09 23:18:23 -04:00
|
|
|
open Open the habits file in $EDITOR
|
|
|
|
|
new Add a new day to the habits file
|
|
|
|
|
left Print today's habits that are not done yet
|
2026-07-09 23:08:42 -04:00
|
|
|
EOF
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ensure_file() {
|
|
|
|
|
mkdir -p "$(dirname "$HABITS_FILE")"
|
|
|
|
|
touch "$HABITS_FILE"
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-24 10:00:31 -04:00
|
|
|
date_add_days() {
|
|
|
|
|
local day="$1" offset="$2" date_arg epoch
|
|
|
|
|
|
|
|
|
|
date_arg="$day $offset days"
|
|
|
|
|
if date -d "$date_arg" +%F >/dev/null 2>&1; then
|
|
|
|
|
date -d "$date_arg" +%F
|
|
|
|
|
return
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# BSD/macOS date. Parse at noon to avoid midnight DST edges.
|
|
|
|
|
epoch="$(date -j -f '%F %H:%M:%S' "$day 12:00:00" +%s)"
|
|
|
|
|
epoch=$((epoch + offset * 86400))
|
|
|
|
|
date -r "$epoch" +%F
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-09 23:08:42 -04:00
|
|
|
open_habits() {
|
|
|
|
|
ensure_file
|
|
|
|
|
"${EDITOR:-vi}" "$HABITS_FILE"
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-24 10:00:31 -04:00
|
|
|
print_day() {
|
|
|
|
|
local day="$1"
|
|
|
|
|
|
|
|
|
|
printf '# %s\n\n' "$day"
|
|
|
|
|
printf '## Habits\n\n'
|
|
|
|
|
while IFS= read -r habit; do
|
|
|
|
|
[ -n "$habit" ] && printf -- '- [ ] %s\n' "$habit"
|
|
|
|
|
done <<< "$HABITS"
|
|
|
|
|
printf '\n## Metrics \n\n'
|
|
|
|
|
while IFS= read -r metric; do
|
|
|
|
|
[ -n "$metric" ] && printf -- '- %s\n' "$metric:"
|
|
|
|
|
done <<< "$METRICS"
|
|
|
|
|
printf '\n'
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-09 23:08:42 -04:00
|
|
|
new_day() {
|
|
|
|
|
ensure_file
|
|
|
|
|
|
2026-07-24 10:00:31 -04:00
|
|
|
local today latest start day tmp added
|
|
|
|
|
today="$(date +%F)"
|
|
|
|
|
latest="$(grep -E '^# [0-9]{4}-[0-9]{2}-[0-9]{2}$' "$HABITS_FILE" | sed 's/^# //' | sort | tail -n 1 || true)"
|
2026-07-09 23:08:42 -04:00
|
|
|
|
2026-07-24 10:00:31 -04:00
|
|
|
if [ -n "$latest" ] && [[ "$latest" > "$today" ]]; then
|
|
|
|
|
echo "Latest habits entry is in the future: $latest" >&2
|
2026-07-09 23:08:42 -04:00
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
|
2026-07-24 10:00:31 -04:00
|
|
|
if [ "$latest" = "$today" ]; then
|
|
|
|
|
echo "Habits entries already exist through $today: $HABITS_FILE"
|
|
|
|
|
return
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
if [ -n "$latest" ]; then
|
|
|
|
|
start="$(date_add_days "$latest" 1)"
|
|
|
|
|
else
|
|
|
|
|
start="$today"
|
|
|
|
|
fi
|
|
|
|
|
|
2026-07-09 23:08:42 -04:00
|
|
|
tmp="$(mktemp)"
|
2026-07-24 10:00:31 -04:00
|
|
|
added=0
|
2026-07-09 23:08:42 -04:00
|
|
|
|
|
|
|
|
{
|
2026-07-24 10:00:31 -04:00
|
|
|
day="$today"
|
|
|
|
|
while [[ "$day" > "$start" || "$day" = "$start" ]]; do
|
|
|
|
|
print_day "$day"
|
|
|
|
|
added=$((added + 1))
|
|
|
|
|
day="$(date_add_days "$day" -1)"
|
|
|
|
|
done
|
2026-07-09 23:08:42 -04:00
|
|
|
cat "$HABITS_FILE"
|
|
|
|
|
} > "$tmp"
|
|
|
|
|
|
|
|
|
|
mv "$tmp" "$HABITS_FILE"
|
2026-07-24 10:00:31 -04:00
|
|
|
echo "Added $added habits entr$( [ "$added" -eq 1 ] && printf 'y' || printf 'ies' ) through $today to $HABITS_FILE"
|
2026-07-09 23:08:42 -04:00
|
|
|
}
|
|
|
|
|
|
2026-07-09 23:18:23 -04:00
|
|
|
remaining_habits() {
|
|
|
|
|
ensure_file
|
|
|
|
|
|
|
|
|
|
local date
|
|
|
|
|
date="$(date +%F)"
|
|
|
|
|
|
|
|
|
|
awk -v date="$date" '
|
|
|
|
|
$0 == "# " date { in_day = 1; next }
|
|
|
|
|
in_day && /^# / { exit }
|
|
|
|
|
in_day && /^## Habits[[:space:]]*$/ { in_habits = 1; next }
|
|
|
|
|
in_day && in_habits && /^## / { exit }
|
|
|
|
|
in_day && in_habits && /^- \[ \] / {
|
|
|
|
|
sub(/^- \[ \] /, "")
|
|
|
|
|
print
|
|
|
|
|
}
|
|
|
|
|
' "$HABITS_FILE"
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-09 23:08:42 -04:00
|
|
|
case "${1:-}" in
|
|
|
|
|
open)
|
|
|
|
|
open_habits
|
|
|
|
|
;;
|
|
|
|
|
new)
|
|
|
|
|
new_day
|
2026-07-24 10:00:31 -04:00
|
|
|
open_habits
|
2026-07-09 23:08:42 -04:00
|
|
|
;;
|
2026-07-09 23:18:23 -04:00
|
|
|
left)
|
|
|
|
|
remaining_habits
|
|
|
|
|
;;
|
2026-07-09 23:08:42 -04:00
|
|
|
-h|--help|help)
|
|
|
|
|
usage
|
|
|
|
|
;;
|
|
|
|
|
*)
|
|
|
|
|
usage >&2
|
|
|
|
|
exit 1
|
|
|
|
|
;;
|
|
|
|
|
esac
|