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.
523 lines
16 KiB
523 lines
16 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 a date-compatible string to generate/open only that file |
|
i.e, $0 today; $0 tuesday; etc |
|
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 |
|
|
|
TAGS can be created by prefixing the name of the tag with '//' |
|
This stores a list of generated links to the tag in an asset. |
|
They can then be reviewed with "$0 tag review <tag>" |
|
This allows for a quick view of the tag's history. Consider it like a project or ongoing task |
|
|
|
Reminders for tasks not completed the day before are transferred to the following day |
|
|
|
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 prev_working_day() { |
|
if [ "$(date ${1+-d "$1"} +%a)" == 'Mon' ]; then modY=3;fi |
|
date +%F -d "$1-${modY:-1} day" |
|
} |
|
|
|
function today() { |
|
date ${1+-d "$1"} +%F |
|
|
|
} |
|
function next_working_day() { |
|
|
|
if [ "$(date ${1+-d "$1"} +%a)" == 'Fri' ]; then modT=3;fi |
|
date +%F -d "${1}+${modT:-1} day" |
|
} |
|
|
|
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 |
|
YESTERDAY=$(prev_working_day) |
|
TODAY=$(today) |
|
TOMORROW=$(next_working_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 |
|
EDIT_FILES=$(echo $CLDIR/{$YESTERDAY,$TODAY,$TOMORROW}) |
|
for i in YESTERDAY TODAY TOMORROW; do |
|
generate_file ${!i} |
|
if [ $i == 'TODAY' ]; then |
|
transfer_items $CLDIR/$YESTERDAY $CLDIR/$TODAY |
|
fi |
|
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 |
|
D=$(date -d "$1" "+%F") |
|
EDIT_FILES=$CLDIR/$D |
|
generate_file $D |
|
transfer_items $CLDIR/$(prev_working_day) $CLDIR/$D |
|
vi $CLDIR/$D |
|
fi |
|
|
|
|
|
asset generate_links |
|
|
|
|
|
for i in $(grep -oP '\s//[^\s]+' $EDIT_FILES|sort -u); do |
|
tag ${i#//} 2>/dev/null |
|
done |
|
|
|
tag 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-fA-F0-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-fA-F0-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;; |
|
tag) TYPE=tag; shift;; |
|
*) break;; |
|
esac |
|
done |
|
|
|
if [ ${1:-0} != 0 ] && [[ $1 =~ (^\[?ASSET:)?[a-fA-F0-9]{8}\]? ]]; then |
|
ID="$(echo "$1"|grep -oE '[a-fA-F0-9]{8}')" |
|
vi "$ASSET_DIR/$ID" |
|
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 ${TYPE-text}" >> "$ADB" |
|
for i in $@; do |
|
K="$(echo $@|cut -d= -f1)" |
|
V="$(echo $@|cut -d= -f2-)" |
|
echo "$ID:$K $V" >> "$ADB" |
|
done |
|
echo "[ASSET:$ID]" > "$ASSET_DIR/$ID" |
|
if [ "$TYPE" != 'tag' ]; then |
|
vi "$ASSET_DIR/$ID" |
|
fi |
|
fi |
|
} |
|
|
|
|
|
function tag() { |
|
while [ $1 ]; do |
|
case $1 in |
|
generate_links) shift; tag_generate_links $@; return;; |
|
review) shift; tag_review $@; return;; |
|
*) break;; |
|
esac |
|
done |
|
if [ "$(tag_get_id $1)" == "" ]; then |
|
asset tag "name=$1" |
|
echo "Created tag $1" |
|
else echo "Tag $1 already exists: $(tag_get_id $1)" >&2; fi |
|
} |
|
|
|
function tag_generate_links() { |
|
export ASSET_DIR="$CLDIR/assets/" |
|
export ADB="$ASSET_DIR/db" |
|
# 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" \)) |
|
|
|
# Get the asset ID for each tag, store it in an array |
|
declare -A REFS |
|
eval REFS=($(for i in $(grep -E '[a-fA-F0-9]{8}:type tag' $ADB|cut -d: -f1); do grep $i:name $ADB|sed 's/^\([a-fA-F0-9]\{8\}\):name \(.*\)/[\2]=\1/'; done)) |
|
|
|
# Look for references to assets |
|
MATCHES="$(grep -oP '\s//[^\s]+' $FILES)" |
|
while read line; do |
|
# Nicely format things |
|
FILE="$(echo $line | cut -d':' -f1)" |
|
FILE="${FILE#$CLDIR/}" |
|
TAG="$(echo $line|cut -d ':' -f2-|grep -oP '//[^\s]+'|tr -d '/')" |
|
if [ "${REFS[$TAG]}" == '' ]; then |
|
echo "$TAG from $FILE doesn't exist. Skipping" |
|
continue |
|
fi |
|
# 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="${REFS[${TAG}]}:link $FILE" |
|
|
|
#Add it to the DB if it's not already there |
|
if ! grep -q "$DB_STRING" $ASSET_DIR/${REFS[${TAG}]} 2>/dev/null; then |
|
echo $DB_STRING >> $ASSET_DIR/${REFS[${TAG}]} |
|
fi |
|
fi |
|
done < <(echo "$MATCHES") |
|
|
|
|
|
|
|
} |
|
|
|
|
|
function transfer_items() { |
|
|
|
TRANSFER='' |
|
|
|
|
|
while read line1; |
|
do while read line2; do |
|
MATCH=0 ; |
|
VAL=$(echo "$(fstrcmp -- "$line1" "$line2") * 100"|bc -l|cut -d . -f1 ); |
|
if [ $VAL -gt 60 ]; then |
|
MATCH=1 |
|
break |
|
fi; |
|
done < <(grep '^\s*[~-]' $2); |
|
if [ $MATCH -eq 0 ]; then |
|
TRANSFER="${TRANSFER}\n$line1" |
|
fi |
|
done < <(grep '^\s*[~-]' $1) |
|
|
|
|
|
|
|
if [ "$TRANSFER" != '' ]; then |
|
echo -e "\nThese incomplete/ongoing items have been transferred from $(basename "$1"):\n $TRANSFER" >> $2 |
|
fi |
|
} |
|
|
|
|
|
|
|
function tag_get_mentions() { |
|
debug "getting tag mentions for $@" |
|
F=$1; |
|
shift |
|
unset BLOCKS |
|
declare -a TAGS |
|
# Get a list of things that look like tasks |
|
TASKS=($(grep -nE '^ ' $F |cut -d: -f1)) |
|
debug "TASKS ${TASKS[@]}" |
|
|
|
# Set last element of the array to be EOF, so that if it matches the last block the below works |
|
TASKS[${#TASKS[@]}]=$(wc -l $F|cut -d' ' -f1) |
|
# Compare them to the line numbers given as arguments |
|
for line in $@; do |
|
debug LINE: $line |
|
for i in $(seq 0 $((${#TASKS[@]} -1 )) ); do |
|
if [ $(( ${TASKS[$i]} >= $line )) == 1 ]; then |
|
if [ $(( ${TASKS[$i]} > $line )) == 1 ]; then |
|
debug "LINE_MATCH_GT: $line > ${TASKS[$i]}" |
|
BLOCKS="$BLOCKS ${TASKS[$(( $i - 1 ))]}"; |
|
else |
|
debug "LINE_MATCH_EQ: $line = ${TASKS[$i]}" |
|
BLOCKS="$BLOCKS ${TASKS[$i]}"; |
|
fi |
|
|
|
break |
|
fi |
|
done |
|
|
|
done |
|
|
|
BLOCKS="$(echo "$BLOCKS"|tr ' ' "\n"|sort -n|uniq)" |
|
|
|
debug "BLOCKS $BLOCKS" |
|
for BLOCK_START in $BLOCKS; do |
|
# Grab from the preceding start of yaml element, to the first line that doesn't start with two pieces of whitespace (i.e the yaml block) |
|
BLOCK="$(sed -rn -e $BLOCK_START',/^ ?\S/p' $F|sed '$d')" |
|
|
|
debug "BLOCK STARTING AT $BLOCK_START\n $BLOCK" |
|
echo -e "$BLOCK"|fmt -w 100 |
|
done |
|
} |
|
|
|
|
|
function tag_get_id() { |
|
export ASSET_DIR="$CLDIR/assets/" |
|
export ADB="$ASSET_DIR/db" |
|
echo $(for i in $(grep ":name $1" $ADB|cut -d: -f1); do grep $i $ADB|grep ':type tag'|cut -d: -f1;done) |
|
} |
|
|
|
function tag_review() { |
|
export ASSET_DIR="$CLDIR/assets/" |
|
export ADB="$ASSET_DIR/db" |
|
cd $CLDIR |
|
ID=$(tag_get_id $1) |
|
for i in $(grep ':link' $ASSET_DIR/$ID|cut -d' ' -f2-); do |
|
echo $i |
|
tag_get_mentions $i $(grep -n //$1 $i|cut -d: -f1) |
|
echo |
|
done |
|
} |
|
|
|
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;; |
|
tag) shift; tag "$@"; exit;; |
|
*ASSET*) asset "$@"; exit;; |
|
help|--help|-h) help; exit;; |
|
transfer) transfer_items $CLDIR/$(today) $CLDIR/$(next_working_day); exit;; |
|
dir) echo $CLDIR; exit;; |
|
*) break;; |
|
esac |
|
done |
|
|
|
captains_log $@
|
|
|