You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

342 lines
10 KiB

#!/bin/bash
PROFILE='default'
function debug() {
echo $DEBUG
if [ ${DEBUG:-0} -ne 0 ]; then
echo "$(date): $@"
fi
}
function help() {
cat << EOF
$0 - Captain's log
Creates/opens files in which to log daily activity.
Files are created with basic prompts.
Create a file in "$CLDIR" called "schedule" (or use $0 schedule edit) with lines in the following format
YYYY-MM-DD Task to review
or
<datestring per date format>=<matching string> Recurring Task to review ; eg
%A=Monday Task for monday
OR
using "$0 schedule <datestring> <task>" will achieve the same thing
Create a file in "$CLDIR" called "template" with the base text to add to each day
If the file is not executable, it is treated as a static template and is copied each day.
The string {{DATE}} can be used to insert a date string. {{DATE <date-compatible format>}} can be used to replace the defualt format.
i.e {{DATE %F}} fill produce the text $(date %F).
If the file is executable, it will be run in the context of the user, and the output will be used instead.
Create a file in "$CLDIR" named "backlog", and it will be opened along with the other daily files, split under "tomorrow"
Directories created under $BASEDIR are treated as profiles (if they exist). The default profile is assumed to be 'default'.
Specify -p <profile> as the first argument to $0 to operate on a different one
Run with "asset" to spin up a new empty file with an asset tag. This is currently assumed to be plain text that complements the log, but makes little sense to included.
At some point this will be expanded to handle other files
Run with "asset <ID>" to open said asset.
Run with "review" ($0 review), and you will get a rundown of complete and incomplete tasks for the week.
Optionally, supply with date-compatible strings as "$0 review <start> [end] to use a range, end defaults to yesterday
A task is considered complete if it matches the regex /^ \*/ and incomplete matching /* \-/
Also considering + to mean "a task that was not planned, but popped up during the course of the day".
For the purposes of review it counts as incomplete, unless prefixed with \*
I've also implemented /^ ~/, implying a continuing task (as opposed to - which I'm sort of considering "not only incomplete, but barely progressed/attempted").
For the purposes of review, ~ counts as Completed.
I'm hoping to capture which tasks are is under-estimated.
Events are also printed, assuming ! below
Notes are generally ignored, but can be started with "^ #", "^ ;", or "^ !" for future functionality
This will evolve with use, but generally (and in order of importance)
! signifies and event worthy of logging
; is a general note possibly worth reviewing
# is for a verbose note, probably not of general interest but was worth noting
If $CLDIR is a git repo (git rev-parse returns 0), it will be updated and auto committed unless --no-git is passed
EOF
}
function review() {
DATE_START=${1:-"-7 days"}
DATE_END=${2:-"yesterday"}
DATE_RANGE=$(( ($(date -d "$DATE_END" "+%s") - $( date -d "$DATE_START" "+%s")) /86400 ))
if [ $DATE_RANGE -lt 0 ]; then
echo "DATE_END is earlier than DATE_START"
exit 1
fi
# Make an array of possible dates to get stuff from
declare -a DATES
# This is stupid. {n..n} notation doesn't work with variables (no substitution) and seq doesn't do reverse ranges
# so we use seq to get the range, then reverse it with tac
for i in $(seq 0 $DATE_RANGE|tac ); do
DATES[${#DATES[@]}]="$(date -d"$DATE_END -$i days" "+%F")"
done
for j in '\*|~' '-|\+' '!' ; do
case $j in
\\*\|\~) echo "Completed Tasks:";;
\-\|\\+) echo "Incomplete Tasks:";;
\!) echo "Events:";;
esac
for i in ${DATES[@]} ; do
if [ -f "$CLDIR/$i" ]; then
T="$(grep -P "^\s($j\s)" "$CLDIR/$i")"
if [ "$T" != "" ]; then
echo -n " ";date "+%A %d %B %Y" -d "$i"
echo "$T"|sed "s/^/\t/"
fi
fi
done
echo
done
}
function schedule() {
if [ ${1:--1} -eq -1 ] || [ "$1" == "edit" ]; then
if [ -f "$CLDIR/schedule"* ];then
vi -p "$CLDIR/schedule"*
else
vi "$CLDIR/scheduled"
fi
exit
fi
if ! [[ $1 =~ = ]] && [ "$(date -d "$1" "+%s")" -lt $(date -d "today" "+%s") ]; then
echo -n "$1 Looks like it's in the past, do you still wan to add it? [y/N]: "
read answer
if ! [[ "$answer" =~ ^[yY] ]]; then
exit
fi
fi
echo "$@" >> "$CLDIR/scheduled"
}
function captains_log() {
# Take into account only the work week.
# Might make sense in the future to check if there are any notes for the intervening days Sat/Sun
# and display them if so
if [ "$(date +%a)" == 'Mon' ]; then modY=3;fi
if [ "$(date +%a)" == 'Fri' ]; then modT=3;fi
YESTERDAY=$(date +%F -d "-${modY:-1} day")
TODAY=$(date +%F)
TOMORROW=$(date +%F -d "+${modT:-1} day")
if [ ${GIT:-1} ] && (git -C $CLDIR rev-parse 2>/dev/null) ; then
GIT=1
else
GIT=0
fi
if ! [ -d "$CLDIR" ]; then
mkdir "$CLDIR"
fi
if [ $GIT -ne 0 ]; then
git -C "$CLDIR" pull -q &
fi
# Create files if they don't exist
if [ ${1-x} == 'x' ]; then
for i in YESTERDAY TODAY TOMORROW; do
generate_file ${!i}
done
if [ -f "$CLDIR/backlog" ]; then
vi $CLDIR/{$YESTERDAY,$TODAY,$TOMORROW,backlog} -s <( echo -e ":vsplit\n:wincmd w\n:next\n:vsplit\n:wincmd w\n:next\n:split\n:wincmd w\n:next")
else
vi -O $CLDIR/{$YESTERDAY,$TODAY,$TOMORROW}
fi
else
generate_file $1
vi $CLDIR/$(date -d "$1" "+%F")
fi
asset generate_links
if [ $GIT -ne 0 ]; then
echo Updating git...
if [ $( git -C "$CLDIR" status -s |wc -l) -ne 0 ]; then
git -C "$CLDIR" add -A
git -C "$CLDIR" commit -qm "Auto-Commit by $(basename $0)"
git -C "$CLDIR" push -q
fi
fi
}
function generate_file() {
# Create files if they don't exist
F=$(date -d "${1:-$(date +%F)}" "+%F")
if ! [ -f "$CLDIR/${F}" ]; then
# If there's a template and it's executable, execute it and output the result to the file,
# If it's not executable, replate {{DATE <date-compatible formate>}} with said date/formate
# Otherwise do the boilerplate
debug "File $F doesn't exist, creating it"
if [ -x "$CLDIR/template" ]; then
debug "From executable template"
"$CLDIR/template" > $CLDIR/${F}
elif [ -f "$CLDIR/template" ]; then
DATE_ARGS="$(grep '{{DATE[^}]*}}' "$CLDIR/template"|tr -d {}|cut -d' ' -f2-)"
if [ "$DATE_ARGS" == 'DATE' ]; then DATE_ARGS='%A %d %B %Y'; fi
debug "From static template with DATE_ARGS=$DATE_ARGS"
sed "s/{{DATE[^}]*}}/$(date "+$DATE_ARGS" -d ${F})/g" $CLDIR/template > $CLDIR/${F}
else
echo -e "$(date "+%A %d %B %Y" -d ${!F})\nWhat do you want to accomplish today?\n\nWhat are your notes for today?\n\nWhat do you need to follow up tomorrow?\n" > "$CLDIR/${!F}"
fi
fi
# Read from the "scheduled" file, and put lines into the appropriate file if extant
if [ -f "$CLDIR/scheduled" ] || [ -f "$CLDIR/schedule" ]; then
while read line; do
SCHEDULED=0
SCHED=$(echo $line|cut -d' ' -f1)
TASK=$(echo $line|cut -d' ' -f2-)
# Check if the schedule is DATE_FORMAT=MATCH_EXPRESSION otherwise assume %F
if ( [[ "$SCHED" =~ = ]] && [[ "$( date "+$(echo "$SCHED"|cut -d'=' -f1)" -d "${F}" )" =~ $(echo $SCHED |cut -d'=' -f2-) ]] ) || ( ! [[ "$SCHED" =~ = ]] && [ "$(date +%F -d $SCHED)" == "${F}" ] ); then
if ! grep -qE "^Scheduled to Review today:" "$CLDIR/${F}"; then
echo "Scheduled to Review today:" >> "$CLDIR/${F}"
fi
if ! grep -qE "^ . $TASK" "$CLDIR/${F}";then
echo " - $TASK" >> "$CLDIR/${F}"
fi
if ! [[ "$SCHED" =~ = ]]; then
SCHEDULED=1
fi
fi
if [ $SCHEDULED -eq 0 ]; then
echo "$line" >> "$CLDIR/scheduled.tmp"
fi
done < <(cat "$CLDIR/scheduled" "$CLDIR/schedule" 2>/dev/null)
fi
# In theory, either everyhing is scheduled OR in the tmp file, so we delete it and do a swapsie
rm "$CLDIR/scheduled" "$CLDIR/schedule" 2> /dev/null
if [ -f "$CLDIR/scheduled.tmp" ]; then
mv "$CLDIR/scheduled.tmp" "$CLDIR/scheduled"
fi
}
function asset_generate_links() {
# Either do files modified in the last 7 days, or all files
if [ "$1" == "all" ]; then
MTIME=''
else
MTIME='-mtime -7'
fi
# Exclude things we don't care about (swap files, git, the db itself)
FILES=$(find ~/.captains_log/default/ -type f $MTIME \! \( -iname *.sw* -o -wholename "*/.git*" -wholename "*/assets/db" \))
# Look for references to assets
MATCHES="$(grep -oE '\[?ASSET:)?[a-zA-Z0-9]+\]?' $FILES)"
while read line; do
# Nicely format things
FILE="$(echo $line | cut -d':' -f1)"
FILE="${FILE#$CLDIR/}"
REF="$(echo $line|cut -d ':' -f2-|grep -oE '[a-zA-Z0-9]{8}')"
# Assets contain a self reference so that it can be easily copied while editing. Ignore these refs
if ! [[ "$REF" =~ $(basename $FILE 2>/dev/null) ]]; then
DB_STRING="$REF:link $FILE"
#Add it to the DB if it's not already there
if ! grep -q "$DB_STRING" $ADB 2>/dev/null; then
echo $DB_STRING >> $ADB
fi
fi
done < <(echo "$MATCHES")
}
function asset() {
export ASSET_DIR="$CLDIR/assets/"
export ADB="$ASSET_DIR/db"
while [ $1 ]; do
case $1 in
generate_links) shift; asset_generate_links $@; return;;
*) break;;
esac
done
if [ ${1:-0} != 0 ]; then
if [[ $1 =~ (\[?ASSET:)?[a-zA-Z0-9]+\]? ]]; then
ID="$(echo "$1"|grep -oE '[a-zA-Z0-9]{8}')"
vi "$ASSET_DIR/$ID"
fi
else
if ! [ -d "$ASSET_DIR" ]; then
mkdir "$ASSET_DIR"
touch $ADB
fi
# Generate a random ID that doesn't already exist
while grep -qi "$ID" "${ADB}"; do
ID="$(openssl rand -hex 4)"
# Alternatively
#hexdump -n 4 -e '/4 "%08X" 1 "\n"' /dev/urandom
done
echo "$ID:type text" >> "$ADB"
echo "[ASSET:$ID]" > "$ASSET_DIR/$ID"
vi "$ASSET_DIR/$ID"
fi
}
BASEDIR="$HOME/.captains_log"
# Check if there are any profiles, otherwise just use the basedir
if [ "$(find $BASEDIR -type d|wc -l)" -gt 1 ]; then
CLDIR="${BASEDIR}/${PROFILE:-default}"
else
CLDIR="$BASEDIR"
fi
while [ $1 ]; do
case $1 in
-p|--profile) CLDIR="$BASEDIR/$2"; shift 2;;
--no-git) GIT=0;shift;;
--debug) DEBUG=1; shift;;
review) shift; review "$@"; exit;;
schedule) shift; schedule "$@";exit;;
asset) shift; asset "$@"; exit;;
*ASSET*) asset "$@"; exit;;
help|--help|-h) help; exit;;
*) break;;
esac
done
captains_log $@