A dmenu calendar.

git clone git://watertao.xyz/programs/calcurse_dmenu.git

commit 1d5b17849f3b75d8270e4fb5c49765a6751bf2fa
parent 75d2766d083b8d87ef79ed29345c9dffe913f585
Author: Jeff <dev@watertao.xyz>
Date:   Tue, 23 Apr 2024 21:40:17 -0700

Added calcurse's multi-day apt capability

Diffstat:
ALICENSE | 1+
AREADME | 191+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcalcurse_dmenu.sh | 261+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
Aconfig.sh | 46++++++++++++++++++++++++++++++++++++++++++++++
Aentries.awk | 165+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aentries.sh | 45+++++++++++++++++++++++++++++++++++++++++++++
Aentries_expand_apts.awk | 25+++++++++++++++++++++++++
Aentries_expand_recur.awk | 169+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aentry_add.awk | 114+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aentry_view.sh | 42++++++++++++++++++++++++++++++++++++++++++
10 files changed, 1039 insertions(+), 20 deletions(-)

diff --git a/LICENSE b/LICENSE @@ -0,0 +1 @@ +Copy this repository into your own responsibility and do with it as you wish. diff --git a/README b/README @@ -0,0 +1,191 @@ +calcurse_dmenu +-------------- +dmenu ui for calcurse calendar program + + +Usage +----- + + $ sh calcurse_dmenu.sh + +WARNING - this program reads and writes to calcurse data files. If +calcurse is installed, as a safety precaution please backup data files +first before running this program. + + +Description +----------- +A dmenu calendar. + +Reads and writes to calcurse data files and as such is fully compatible +with calcurse (Note WARNING above). calcurse is not required to run a +fully functioning calcurse_dmenu. + +The view and selection of calendar, todo, history, and alarm entries +is a 100% dmenu experience. To edit notes attached to entries, an $EDITOR +of your choice (see config.sh) is employed. A more complete view of +entries is displayed by a pager of your choice (see config.sh). + +This is a raw deal. Although notes may be edited, entries themselves +are absolute. To make a change, simply retype the new entry and remove +the old. Although minor, it is inconvenient and on the table for future +development. + +This is purely a dmenu/pager/editor experience. Not a single graphical +representation of a calendar to be found. That's for the sake of +efficiency. The entire goal of this is for speed of service and use. +It provides a very fast access to your calcurse calendar and an equally +swift interaction. If calcurse is installed, typing 'calcurse' into +the dmenu prompt launches calcurse for the graphical experience. + + +Add entry syntax +---------------- +date #recurrance :: description + +### date (optional) + +The date is any human readable date string parsed by linux's coreutils +'date' user command. See your date(1) man page for details. + +Note: For bsd/et al installs, you'll need to edit the entry_add.awk +file to meet your needs. The easiest is to rewrite this using awk's +builtin date functions, which will likely lead to stricter date entry +when adding new calcurse_dmenu entries. + +The date may be a range, which is determined by a dash between two +dates. If times are specified, entries are stored as calcurse apts, +otherwise calendar entries are stored as calcurse events. See Examples +below. + +### recurrance (optional) + +A recurring entry is determined by deploying the '#' symbol after the +date. A regex representation of the recurrance syntax is: + + #[0-9]+[dmyDMY] (end date)? + +In other words, pound symbol, following by lapse before next recurrance, +followed by type of time lapse - day, week, month, or year, followed by +the optional end date. Without the optional end date the recurrance is +infinite; birthdays for example. See Examples below. + +### description + +Description is the only required information to create a basic event +entry. Without a date, the entry date is set for 'now' or rather today. +Description can be almost anything, so long as it does not match the +pattern of a note marker in the data files. So the following regex +representation is not allowed in the description: + + >[a-f0-9]{40} + +Example: + + >123456789a123456789b123456789c123456789d + +Also wise to refrain from using '::' in description. + + +Tag Commands +------------ +Tag commands are tags, set by default and configurable in config.sh. +They comprise currently five functions: + +- :n - Entry note create/edit +- :r - Entry remove +- :t - Show todo entries +- :h - Show calendar history entries +- :a - Show alarm entries + +The entry note and remove tags work by tagging an entry's entire line. +So for the following entry: + + Mon 29 > Work week begins {1W} <4 + +The following returned selection will open $EDITOR to edit entry note: + + Mon 29 > Work week begins {1W} <4:n + +And to remove/delete the entry: + + Mon 29 > Work week begins {1W} <4:r + +For todo, history, and alarm entries, simply return the tag in dmenu's +prompt. eg: + + :h + + +About Calendar Entry Types +-------------------------- +Calcurse specifies two types of calendar entries. + +1. Apts - time specificion, one or more days. +2. Events - no time specification, one day. + +Calcurse compliance dictates that if an event is to span more than one +day, it must be recurred. So sticking to calcurse compliancy, a multi- +day event would be enterred using recurrance in calcurse_dmenu by: + + today #3d :: Some three day event + +If a calendar entry begins with a specified time, it is considered an +appointment (apt) and can last as many days as your system can handle. +To set an apt in calcurse_dmenu, it could be as simple as: + + 7:00 :: wake up + +Or something spanning several days: + + 7:00 - 5 days 17:00 :: 5 day retreat + +Or the same retreat that recurs every year: + + 7:00 - 5 days 17:00 #1y :: 5 day retreat + + +Alarm +----- +Alarms are currently under development. The program is equiped to edit +alarm entries (not a calcurse function by the way). The alarm +implementation will be a stand alone process outside of calcurse_dmenu. +It is safe to expect a functioning example of this in the future with +documentation. + + +Requirements +------------ + +- linux (gnu coreutils date command) +- dmenu +- an $EDITOR +- a pager, more or less +- posix compliant shell tools: awk/sed/grep/sort + + +Todo +---- +- Entry edit capability +- Alarms + + +Examples +-------- + + 10 min :: Doing something in ten minutes from now + + thu 9:0 - 17:0 :: work on this thursday from 9a to 5p + + jan 12 #1y :: Important person's birthday + + sun #1w 10 years :: Day of rest, every Sunday, for ten years! + + 5:00 #1d jun 12 2042 :: Wake up every day at 5am until 07/12/42 + +Note on time: normally using the date command, you can specify time +using am and pm, however due to calcurse_dmenu parsing of this line, +the program looks for full-ish mil times, with a colon and at least a +single digit on both sides of that colon. So specifying 5am time +looks like: 5:0 or 5:00. + diff --git a/calcurse_dmenu.sh b/calcurse_dmenu.sh @@ -1,27 +1,248 @@ -#!/bin/ksh -. ~/scripts/local_lib.sh +#!/bin/sh -start_date="%(start:%Y %m %d)" -cal_notes="/home/$USER/.local/share/calcurse/notes" +#. $LOCAL_LIB/tao/local.vars +PROGDIR="$(cd -- "$(dirname -- "$0")" && pwd)" +. $PROGDIR/config.sh -function cu(){ - rm "$t" 2>/dev/null +[ -d "$cal_dir" ] || mkdir -p "$cal_dir" +[ -d "$cal_notes" ] || mkdir -p "$cal_notes" +[ -f "$cal_apts" ] || touch "$cal_apts" +[ -f "$cal_todo" ] || touch "$cal_todo" +[ -f "$cal_alarm" ] || touch "$cal_alarm" + +t="$(mktemp)" && u="$(mktemp)" && v="$(mktemp)" && x="$(mktemp)" && y="$(mktemp)" || exit 1 + +cu(){ + rm "$u" "$t" "$v" "$x" "$y" 2>/dev/null + exit 0 +} + +trap cu INT + +cal_lno_dmenu_string(){ + l="$(<$u sed -n "s/^.*<\([0-9]\+\)\(${rm_tag}\)*\(${n_tag}\)*$/\1/p")" && \ + [ -n "$l" ] && \ + return 0 || return 1 +} + +__cal_title(){ + s="$S_TITLE" + case "$typeflag" in + "h") s="$S_HISTORY" ;; + "t") s="$S_TODO" ;; + "a") s="$S_ALARM" ;; + esac + printf "%s" "$s" +} + +__cal_evt_sel(){ + sh "$PROGDIR/entries.sh" -t "$typeflag" | \ + $dmenu -i -p "$(__cal_title)" || \ + return 1 + #cu +} + +cal_evt_sel(){ + # u = tmp file storing selected dmenu string + # v = tmp file storing full data line + # l = db line number + # n = note filename + __cal_evt_sel > $u && \ + cal_lno_dmenu_string && \ + < "$db_file" sed -n "${l}p" > $v && \ + n="$(<$v grep -o "$note_pattern" | tr -d ">")" && \ + return 0 || return 1 +} + +cal_rm_note_file(){ + f="${cal_notes}/${n}" + [ -f "$f" ] && rm "$f" + return 0 +} + +cal_is_todo(){ [ "$typeflag" = "t" ]; } + +cal_is_alarm(){ [ "$typeflag" = "a" ]; } + +cal_rm_note_ref(){ + cal_is_todo && s="" || s=" " + sed -i "${l}s/^\(.*\)${note_pattern}${s}\(.*\)$/\1\2/" "$db_file" +} + +cal_rm_entry(){ + [ "$l" -gt 0 ] && \ + sed -i "${l}d" "$db_file" && \ + cal_rm_note_file +} + +cal_note_view(){ + #"$term" $pager "${cal_notes}/${n}" + "$term" $EDITOR "${cal_notes}/${n}" +} + +cal_entry_view(){ + $term sh $PROGDIR/entry_view.sh $u "$cal_notes/$n" "$typeflag" +} + +grep_prep(){ + # escape special grep chars + b="$(<$v sed -e 's#\([][\$\*\^]\)#\\\1#g')" +} + +flag_reset(){ + # Type flags + # c > main calendar + # h > historia + # t > todo + typeflag="c" +} + +cal_note_create(){ + # Add note + >$t && "$term" $EDITOR $t + if [ -s "$t" ] + then + # Generate note file name + n="$(</dev/urandom tr -dc '0-9a-f' | head -c40)" + # Copy temp to new note file + cp $t "${cal_notes}/${n}" + # Update apts db entry with note reference + if cal_is_todo; then + sed -i " + ${l}s/\(^\[[0-9]\+\]\)\(.*\)$/\1>${n}\2/ + " "$db_file" + else + sed -i " + ${l}s/\(^[0-9\/]\+ \)\(@[- 0-9\>\/:@]\+\)\({.*} \)*\(|\)/\1\2\3>${n} \4/ + ${l}s/\(^[0-9\/]\+ \)\(\[[0-9]\+\] \)\({.*} \)*/\1\2\3>${n} / + " "$db_file" + fi + fi +} + +sort_entries(){ + [ -n "$1" ] && \ + <$v grep -E "$1" | \ + sed 's#^\(..\)/\(..\)/\(....\)#\3\1\2 \1/\2/\3#' | \ + sort | \ + cut -d" " -f2- >> $x && \ + return 0 || return 1 +} + +sort_todo(){ + # Sort rank then desc + # Move possible note for sorting, then back + [ -n "$1" ] && \ + <$v grep "$1" | \ + sed "s/^\(\[[0-9]\]\)\(${note_pattern}\)\( .*\)$/\1\3\2/" | \ + sort | \ + sed "s/^\(\[[0-9]\]\)\( .*\)\(${note_pattern}\)$/\1\3\2/" >> $x && \ + return 0 || return 1 } -function calcurse_view(){ - calcurse -G \ - --filter-type "event,apt,recur-event,recur-apt" | \ - $dmenu -i | \ - grep -o ">[a-f0-9]\+" | \ - tr -d ">\n" + +cal_view(){ + + if cal_evt_sel + then + # Remove if tagged :::r + if <$u grep -q "$rm_tag$" + then + cal_rm_entry + + elif <$u grep -q "$n_tag$" + then + # Open note in pager + if [ -n "$n" ] + then + if [ -f "$cal_notes/$n" ] + then + cal_note_view + + # If note empty, rm it + if [ ! -s "$cal_notes/$n" ] + then + cal_rm_note_file + cal_rm_note_ref + fi + fi + else + cal_note_create + fi + else + cal_entry_view + fi + else + # Emergency exit + <$u grep -q "^exit$\|^quit$" && cu + + # If line seperator for today/tomorrow/yesterday, do nothing + <$u grep "^$S_SEPERATOR$" && return 0 + + # If from historia, back to main list + [ "$typeflag" = "h" ] && flag_reset && return 0 + + [ "$typeflag" = "t" ] && [ ! -s $u ] && flag_reset && return 0 + + [ "$typeflag" = "a" ] && [ ! -s $u ] && flag_reset && return 0 + + # Exit if selection empty + <$u grep -q "^[[:space:]]*$" || [ ! -s $u ] && return 1 + + # launch calcurse + <$u grep -q "^calcurse$" && { $term calcurse; return 1; } + + # Historia + <$u grep -q "^${h_tag}$" && typeflag="h" && return 0 + + # Todo + <$u grep -q "^${t_tag}$" && typeflag="t" && return 0 + + # Alarm + <$u grep -q "^${a_tag}$" && typeflag="a" && return 0 + + # Prepare for awk parse + + # Add 2 colons for awk parsing + # This allows for quick event entries + # for current day. + # Cal entry: date(s)::description + # Todo entry: rank(optional)::description + <$u grep -q "^.*::.\+$" || sed -i 's/^/::/' $u + + # Add new entry to apts file + # Parse new entry with awk + # Sort new list of entries in following order: + # 1 > event recurring + # 2 > apt recurring + # 3 > apt + # 4 > event + + >$x + >$v + [ -f "$db_file" ] && cp "$db_file" "$v" + + awk -vtypeflag="$typeflag" -f "$entry_add_awk" $u >> $v && \ + sort_entries "^[[:digit:]/]{10} \[[[:digit:]]+\] {" && \ + sort_entries "^[[:digit:]/]{10} @.{29}{" && \ + sort_entries "^[[:digit:]/]{10} @.{28}[>|]" && \ + sort_entries "^[[:digit:]/]{10} \[[[:digit:]]+\] [^{]" && \ + sort_todo "^\[[1-9]\]" && \ + sort_todo "^\[0\]" && \ + cp $x "$db_file" && \ + return 0 + + return 1 + fi } -t="$(mktemp)" -printf "%s\n" "Add" "View" | $dmenu -i -p "[ calcurse ]" > $t +# Set typeflag to Calendar to kick things off +flag_reset -<$t grep -q -i "add" && \ - calcurse_add && cu && exit 0 -<$t grep -q -i "view" && \ - a="$(calcurse_view)" - [ -n "$a" ] && [ -f "${cal_notes}/${a}" ] && \ - "$term" less -N "${cal_notes}/${a}" && cu && exit 0 +#And so it begins +while + db_file="$cal_apts" + cal_is_todo && db_file="$cal_todo" + cal_is_alarm && db_file="$cal_alarm" + cal_view +do :; done cu diff --git a/config.sh b/config.sh @@ -0,0 +1,46 @@ +#!/bin/sh + +#[ -z "$PROGDIR" ] && exit 1 +# dmenu command +a="sxmo_dmenu.sh dmenu" +for i in $a; do [ -f "/usr/bin/${i}" ] && dmenu="$i" && break; done + +# term command +a="sxmo_terminal.sh st" +for i in $a; do [ -f "/usr/bin/${i}" ] && term="$i" && break; done + +# Remove entry tag +rm_tag=":r" +# Historia tag +h_tag=":h" +# Todo tag +t_tag=":t" +# Note tag +n_tag=":n" +# Alarm tag +a_tag=":a" + +# Strings +S_YESTERDAY="Yesterday" +S_TODAY="Today" +S_TOMORROW="Tomorrow" +S_SEPERATOR="-----------------" +S_HISTORY="Historia" +S_TODO="Todo" +S_ALARM="Alarm" +S_TITLE="" +# Entry select view colors +DATE_CLR=6 +DESC_CLR=2 +RECUR_CLR=242 + +#pager="less -N -c" +pager="less -R --use-color -c --tilde" +EDITOR="${EDITOR:-"vim"}" +cal_dir="/home/$USER/.local/share/calcurse" +cal_notes="$cal_dir/notes" +cal_apts="$cal_dir/apts" +cal_todo="$cal_dir/todo" +cal_alarm="$cal_dir/alarm" +entry_add_awk="$PROGDIR/entry_add.awk" +note_pattern=">[a-f0-9]\{40\}" diff --git a/entries.awk b/entries.awk @@ -0,0 +1,165 @@ +#!/bin/awk +function date_f(s) +{ + b_first_date = ( first_date == 0 ) + ( b_first_date ) && p_date_type = date_type + ( b_first_date ) && date_type = "day" + + # m/d/y date to ymd + ( s ~ /\// ) && s = sprintf( "%s%s%s", substr(s, 7, 4), substr(s, 1, 2), substr(s, 4, 2) ) + + # ymd to mktime fmt + a = sprintf( "%s %s %s 00 00 00", substr(s, 1, 4), substr(s, 5, 2), substr(s, 7, 2) ) + + b = mktime( a ) + if ( s == strftime( "%Y%m%d" ) ) { + r = sprintf( "%s %s", stoday, strftime( "%a %d", b ) ) + + } else if ( s == tomorrow ) { + r = sprintf( "%s %s", stomorrow, strftime( "%a %d", b ) ) + + } else if ( s == yesterday ) { + r = sprintf( "%s %s", syesterday, strftime( "%a %d", b ) ) + + } else if ( b < plus7 && b > minus7 ) { + r = sprintf( "%s", strftime( "%a %d", b ) ) + ( b_first_date ) && date_type = "week" + + } else if (substr(s,1,4) == cyr){ + r = strftime("%b %d %a", b) + ( b_first_date ) && date_type = "year" + }else{ + r = strftime("%Y-%m-%d %a", b) + ( b_first_date ) && date_type = "etal" + } + return r +} + +function date_fmt_from_regex( rgx, mmax ) +{ + x = $0 + e = 1 + + while ( match(x, rgx ) > 0 && first_date != mmax ){ + sub( rgx, date_f( substr( x, RSTART, RLENGTH ) ), x ) + $0 = sprintf( "%s%s", substr($0, 1, e - 1 ), x ) + x = substr( x, RSTART + RLENGTH ) + e = e + RSTART+RLENGTH - 1 + first_date++ + } +} + +function print_seperator() +{ + if ( date_type == "day" ){ + if ( $1 != pdate ){ + pdate = $1 + print sseperator + } + }else if ( p_date_type != date_type ){ + print sseperator + } +} + +function entry_out() +{ + if ( ! dp ) { + print $0 + return 0 + } + + m_date = ""; m_desc = ""; m_note = "" + + # Format dates + first_date = 0 + date_fmt_from_regex( "^[0-9]{8}", 1 ) + date_fmt_from_regex( "[0-9]{2}/[0-9]{2}/[0-9]{4}", -1 ) + + #Note + if ( gsub( />[0-9a-f]{40}/, "" ) > 0 ){ + #match($0,/<note>/,m_note) + m_note = "<note> " + } + + if ( e_type == "apt" ){ + # Match recurrence brackets and move + if ( match($0, /\{.*\}/ )){ + gsub( /<[0-9]+$/, substr($0, RSTART, RLENGTH)" &" ) + sub( /[[:space:]]\{[[:alnum:]\->[:space:]]+\}[[:space:]]/, "" ) + } + apti = index( $0, "|" ) + apta = substr( $0, 1, apti - 1 ) + aptb = substr( $0, apti + 1 ) + split( apta, dates, " -> " ) + gsub( /[[:space:]]$/, "", dates[2] ) + if ( dates[1] == dates[2] ){ + m_date = gensub( "@ ", "", "g", dates[1] ) + }else{ + split( dates[1], date1, " @ " ) + split( dates[2], date2, " @ " ) + if( date1[1] == date2[1] ){ + m_date = sprintf( "%s %s - %s", date1[1], date1[2], date2[2] ) + }else{ + m_date = sprintf( "%s %s - %s %s", date1[1], date1[2], date2[1], date2[2] ) + } + } + gsub( /<[0-9]+$/, m_note "&", aptb ) + + if ( dp ) { print_seperator() } + print m_date, ">", aptb + + } else if ( e_type == "evt" ) { + # Match recurrence brackets and move + if ( match($0, /\{.*\}/ )){ + gsub( /<[0-9]+$/, substr($0, RSTART, RLENGTH) " &" ) + sub( /\{[[:alnum:]\->[:space:]]+\}/, "" ) + } + gsub( /\[[0-9]+\][[:space:]]*/, "> " ) + sub( /<[0-9]+$/, m_note "&" ) + + if ( dp ) { print_seperator() } + print $0 + + } else if ( e_type == "todo" ) { + sub( /<[0-9]+$/, m_note "&" ) + print $0 + } + + return 0 +} + +BEGIN{ + cdate = strftime( "%Y%m%d" ) + epoch = strftime( "%s" ) + cyr = strftime( "%Y" ) + ymd_x = "^[0-9]{8}" + e_type = "" + dmax_ymd = int(strftime( "%Y%m%d", dmax )) +} + +{ + if ( match( $0, ymd_x" @" )){ + e_type = "apt" + } else if ( match( $0, ymd_x" \\[" )){ + e_type = "evt" + } else if ( match( $0, /^\[/ )){ + e_type = "todo" + } + # if showing hereafter or hitherto today, print, else exit + if ( ( $1 >= cdate && caltype == "c" ) || ( $1 < cdate && caltype == "h" ) ){ + if ( $1 <= dmax_ymd ){ + entry_out() + } + next + } else if ( $1 >= cdate && caltype == "a" ){ + if ( $1 <= dmax_ymd ){ + entry_out() + } + next + } else if ( caltype == "t" ) { + entry_out() + next + } else { + exit + } +} diff --git a/entries.sh b/entries.sh @@ -0,0 +1,45 @@ +#!/bin/sh + +[ -z "$PROGDIR" ] && . "$LOCAL_LIB/tao/local.vars" +[ -z "$cal_dir" ] && . "$PROGDIR/config.sh" + +# Set t option to h for history or t for todo +# Default is c +getopts :t: t +caltype="${OPTARG:-"c"}" +shift $(($OPTIND - 1)) + +# First optional param, max day +dmax="${1:-"$(date -d "5 Year" +%s)"}" +datapretty="${2:-"1"}" + +f="$cal_apts" +[ "$caltype" = "h" ] && sflag="" || sflag="-r" +[ "$caltype" = "t" ] && f="$cal_todo" +[ "$caltype" = "a" ] && f="$cal_alarm" + +# sed - for sorting purposes, format date for apt/evt's +# and move the note out of the way in todo's +<"$f" awk \ + -v dmax="$dmax" \ + -f "$PROGDIR/entries_expand_recur.awk" | \ +awk \ + -v dmax="$dmax" \ + -f "$PROGDIR/entries_expand_apts.awk" | \ +sed " s/^\([0-9]\{2\}\)\/\([0-9]\{2\}\)\/\([0-9]\{4\}\)/\3\1\2/ + s/^\(\[[0-9]\]\)\(${note_pattern}\)\( .*\)$/\1\3\2/" | \ +sort ${sflag} | \ +awk \ + -v dmax="$dmax" \ + -v plus7="$(date -d "7 days" +%s)" \ + -v minus7="$(date -d "-7 days" +%s)" \ + -v tomorrow="$(date -d "1 day" +%Y%m%d)" \ + -v yesterday="$(date -d "-1 day" +%Y%m%d)" \ + -v stoday="$S_TODAY" \ + -v stomorrow="$S_TOMORROW" \ + -v syesterday="$S_YESTERDAY" \ + -v sseperator="$S_SEPERATOR" \ + -v caltype="$caltype" \ + -v dp="$datapretty" \ + -f "$PROGDIR/entries.awk" | \ +tac diff --git a/entries_expand_apts.awk b/entries_expand_apts.awk @@ -0,0 +1,25 @@ +#!/bin/awk +function date_to_number(s){ + return sprintf( "%s%s%s", substr(s,7,4), substr(s,1,2), substr(s,4,2) ) +} +function number_to_date(s){ + return sprintf( "%s/%s/%s", substr(s,5,2), substr(s,7,2), substr(s,1,4) ) +} +{ + # If not apt, skip to next record + if ( substr($0, 12, 1) != "@" ){ print $0; next } + + # If apt range days match, skip and go to next + d1 = substr($0,1,10) + d2 = substr($0,23,10) + if (d1 == d2) { print $0; next } + + d1 = date_to_number(d1) + d2 = date_to_number(d2) + day_total = d2 - d1 + 1 + sub(/<[[:digit:]]+$/, "( "day_total" day apt ) &", $0) + + for (i=d1; i<=d2; i++){ + print number_to_date(i), substr($0,12) + } +} diff --git a/entries_expand_recur.awk b/entries_expand_recur.awk @@ -0,0 +1,169 @@ +#!/bin/awk + +function mdy_to_epoch( s ){ + d = sprintf( "%s %s %s 00 00 00", substr(s,7,4), substr(s,1,2), substr(s,4,2) ) + return mktime( d ) +} + +function leapyear( y ){ + + # Determine if y is leap year + yy = "Y" + + if ( y/4 ~ int_x ){ + y/100 ~ int_x && y/400 !~ int_x ? yy = "Y" : yy = "L" + } + return yy +} + +function set_recur_date( d ){ + + if ( !d ) { exit 1; } + + if ( recur_type == "D" || recur_type == "W" ){ + + d += interval_total + + } else { + sy = int(substr( recur_date_mdy, 7, 4 )) + sm = int(substr( recur_date_mdy, 1, 2 )) + + if ( recur_type == "M" ) + { + sx = sm + interval + smm = sm + while ( sm < sx ) { + + # Check for year change + while ( smm > 12 ){ + smm -= 12 + sy++ + } + + # If february, check for leap year + if ( smm == 2 && leapyear( sy ) == "L" ){ + mm = "LM" + }else{ + mm = smm + } + + d += m[m[mm]] + sm++ + smm++ + } + + } else if ( recur_type == "Y" ) + { + sx = sy + interval + while ( sy < sx ) { + + # If after Leap month (2 - february), + # check for the next year's leap status + # to determine number of days to increment + # the year with. + sychk = sm > 2 ? sy + 1 : sy + mm = leapyear( sychk ) + + #print sy, "Leap with sychk", sychk, "and month", sm, "?", mm + d += m[mm] + sy++ + } + } + } + return d +} + +BEGIN{ + m[1] = 31 + m[2] = 28 + m["LM"] = 29 + m[3] = 31 + m[4] = 30 + m[5] = 31 + m[6] = 30 + m[7] = 31 + m[8] = 31 + m[9] = 30 + m[10] = 31 + m[11] = 30 + m[12] = 31 + m[28] = 2419200 + m[29] = 2505600 + m[30] = 2592000 + m[31] = 2678400 + m["W"] = 604800 + m["D"] = 86400 + m["H"] = 3600 + m["M"] = 60 + m["L"] = 31622400 + m["Y"] = 31536000 + + int_x = "^[0-9]+$" + #dmax_mdy = strftime( "%Y%m%d", dmax ) +} + +{ + #if ( $1 > dmax_mdy ) { exit } + $0 = sprintf( "%s <%s", $0, NR) + print $0 + + # Do some stuff if recurring event + # Basically print out all recurrances + # up to event max or date max, which + # ever earliest + + if ( index( $0, "{" ) > 0 ){ + + recur_a = index( $0, "{" ) + recur_b = index( $0, "}" ) + split ( substr( $0, recur_a, recur_b - recur_a ), recur, " " ) + + interval = int( substr( recur[1], match(recur[1], /[0-9]+/), RLENGTH ) ) + recur_type = toupper( substr(recur[1], match(recur[1], /[ywmdYWMD]{1}/), 1 )) + interval_total = m[ recur_type ] * interval + + # If recurring event has max date, get values + if ( recur[2] == "->" ){ + entry_max = mdy_to_epoch( recur[3] ) + }else{ + entry_max = dmax + } + + recur_date_mdy = $1 + recur_date = mdy_to_epoch( $1 ) + recur_date = set_recur_date( recur_date ) + recur_date_mdy = strftime( "%m/%d/%Y", recur_date ) + + if ( match( $0, /^[0-9\/]{10} @ ..:.. -> / ) > 0 ){ + recur_date_mdy_b = substr( $0, RLENGTH+1, 10 ) + recur_date_b = mdy_to_epoch( recur_date_mdy_b ) + recur_date_b = set_recur_date( recur_date_b ) + recur_date_mdy_b = strftime( "%m/%d/%Y", recur_date_b ) + } + + while (recur_date <= dmax && recur_date <= entry_max) { + + r = $0 + + # Replace entry date with recur date + gsub( /^[0-9\/]{10}/, recur_date_mdy, r ) + ( recur_date_b > 0 ) && gsub( /-> [0-9\/]{10} @/, sprintf("-> %s @", recur_date_mdy_b), r ) + + # Print recurrance + print r + + # Set recur_date to next recurrance + d = set_recur_date( recur_date ) + if ( recur_date == d ) { exit 1 } + recur_date = d + recur_date_mdy = strftime( "%m/%d/%Y", recur_date ) + + if ( recur_date_b > 0 ){ + d = set_recur_date( recur_date_b ) + if ( recur_date_b == d ) { exit 1 } + recur_date_b = d + recur_date_mdy_b = strftime( "%m/%d/%Y", recur_date_b ) + } + } + } +} diff --git a/entry_add.awk b/entry_add.awk @@ -0,0 +1,114 @@ +#!/bin/awk + +function get_date( s, f ){ + gsub( x_whtspc, "", s ) + date_good = "date -d \""s"\" +\""f"\" 2>/dev/null" | getline r + close( r ) + #if ( !date_good ) { exit 1 } + return r +} + +function mktime_format( s ){ + return get_date( s, "%Y %m %d %H %M 00" ) +} + +function mkapt_time( s ){ + r = mktime( s ) + return strftime( "%m/%d/%Y @ %H:%M", r ) +} + +BEGIN{ + x_whtspc = "^[[:space:]]*|[[:space:]]*$" + x_recur_full = "#[0-9]+[a-zA-Z].*$" + x_recur = "^[0-9]+[a-zA-Z]" +} +{ + # split dates from description into array 'a' + split( $0, a, "::" ) + + # Trim whitespace + gsub( x_whtspc, "", a[2] ) + gsub( x_whtspc, "", a[1] ) + + # Must have a description. + if ( ! a[2] ) { exit 1 } + + # Todo entry: a[1] = optional rank, a[2] = description + if ( typeflag == "t" ){ + + # Rank number, default 0 + if ( a[1] ){ b = a[1] } else { b = 0 } + + entry = sprintf( "[%s] %s", b, a[2] ) + + # Skip all else for todo entry + next + } + + # Extract recurrent value from a[1] if present + if ( match( a[1], x_recur_full ) > 0 ){ + + # Leave the pound sign behind, add one to RSTART + recur_full = substr( a[1], RSTART + 1 ) + match( recur_full, x_recur ) + recur = toupper( substr( recur_full, 1, RLENGTH ) ) + recur_date = substr( recur_full, RLENGTH + 1 ) + if (recur_date){ + recur_date = sprintf( " -> %s", get_date( recur_date, "%m/%d/%Y" ) ) + if ( ! date_good ) { exit 1 } + }else{ + recur_date = "" + } + recur = sprintf( " {%s%s} ", recur, recur_date ) + + # Remove from a[1] for rest of date processing + sub( x_recur_full, "", a[1] ) + } + + # Split start and possible end date into array 'b' + bl = split( a[1], b, "-" ) + + # Use external date command to format for mktime + if ( ! ( c_a = mktime_format( b[1] ))) { exit 1; } + c_a = mktime( c_a ) + + if ( a[1] ~ /[0-9]+:[0-9]+/ || a[1] ~ /min|minute|hour/ ) { + time_a = strftime( "%m/%d/%Y @ %H:%M", c_a ) + if ( bl > 1 ) { + # First check if date 2 is give as only a time + if ( b[2] ~ /^[[:space:]]*[0-9]+:[0-9]+[[:space:]]*$/ ){ + gsub( x_whtspc , "", b[2] ) + gsub( /:/, " ", b[2] ) + c_b = sprintf( "%s %s 00", strftime("%Y %m %d", c_a), b[2] ) + }else{ + # Now try to parse date 2 as it were an addition to first date + # eg: "1 hour" from date 1 + c_b = mktime_format(sprintf( "%s %s", b[1], b[2] )) + if ( ! date_good ) { + # Now try to make full date/time from date 2 + c_b = mktime_format( b[2] ) + if ( ! date_good ) { + # If all tries fail then exit + exit 1; + } + } + } + time_b = mkapt_time(c_b) + }else{ + time_b = time_a + } + entry_type = recur ? "apt_recur" : "apt" + entry = sprintf("%s -> %s%s|%s", time_a, time_b, recur, a[2]) + + }else{ + entry_type = recur ? "evt_recur" : "evt" + + c_a = strftime("%m/%d/%Y", c_a) + + ( ! recur ) && recur = " " + entry = sprintf("%s [1]%s%s", c_a, recur, a[2]) + } +} +END{ + if ( entry ) { print entry } +} diff --git a/entry_view.sh b/entry_view.sh @@ -0,0 +1,42 @@ +#!/bin/sh + +[ -z "$PROGDIR" ] && . "$LOCAL_LIB/tao/local.vars" +[ -z "$cal_dir" ] && . "$PROGDIR/config.sh" + +# First param must be set to tmp file of dmenu line selection +[ ! -f "$1" ] && exit 1 + +# Second param is the note tmp file, if applicable +[ -n "$2" ] && [ -f "$2" ] && n="$2" || n="" + +# Third param is typeflag, if todo, print it w desc color +[ -n "$3" ] && tf="$3" || tf="c" + +<$1 sed "s/ \(<note> \)*<[0-9]\+\($rm_tag\)*\($n_tag\)*$//" | awk -v tf="$tf" ' + +BEGIN{ x = 0 } +{ + if ( FNR == 1 ) { x++ } + if ( x == 1 ){ + if ( tf == "t" ) { + print "'"$(tput setaf "$DESC_CLR")"'"$0 + print "" + } else { + i = index( $0, ">" ) + a = substr( $0, 1, i - 2 ) + if ( match( $0, /\{[^}{]+\}$/ ) ){ + b = substr( $0, i + 2, RSTART - i - 2 ) + c = substr( $0, RSTART ) + }else{ + b = substr( $0, i + 2 ) + c = "" + } + print "'"$(tput setaf "$DATE_CLR")"'"a + if (c) { print "'"$(tput setaf "$RECUR_CLR")"'"c } + print "'"$(tput setaf "$DESC_CLR")"'"b + print "" + } + }else{ + print + } +}' - "$n" | $pager