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:
A | LICENSE | | | 1 | + |
A | README | | | 191 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
M | calcurse_dmenu.sh | | | 261 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------ |
A | config.sh | | | 46 | ++++++++++++++++++++++++++++++++++++++++++++++ |
A | entries.awk | | | 165 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | entries.sh | | | 45 | +++++++++++++++++++++++++++++++++++++++++++++ |
A | entries_expand_apts.awk | | | 25 | +++++++++++++++++++++++++ |
A | entries_expand_recur.awk | | | 169 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | entry_add.awk | | | 114 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | entry_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