summary refs log tree commit diff
path: root/Functions/Calendar/calendar_add
blob: eded25b2a7f8f168d1d3e05fc0de0da985944d75 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
#!/bin/zsh
# All arguments are joined with spaces and inserted into the calendar
# file at the appropriate point.
#
# While the function compares the date of the new entry with dates in the
# existing calendar file, it does not do any sorting; it inserts the new
# entry before the first existing entry with a later date and time.

emulate -L zsh
setopt extendedglob

local context=":datetime:calendar_add:"

local calendar newfile REPLY lastline opt
local -a calendar_entries lockfiles reply
integer my_date done rstat nolock nobackup new_recurring old_recurring
local -A reply parse_new parse_old recurring_uids

autoload -U calendar_{parse,read,lockfiles}

while getopts "BL" opt; do
  case $opt in
    (B)
    nobackup=1
    ;;

    (L)
    nolock=1
    ;;

    (*)
    return 1
    ;;
  esac
done
shift $(( OPTIND - 1 ))

# Read the calendar file from the calendar-file style
zstyle -s $context calendar-file calendar ||
  calendar=~/calendar
newfile=$calendar.new.$HOST.$$

local addline="$*"
if ! calendar_parse $addline; then
  print "$0: failed to parse date/time" >&2
  return 1
fi
parse_new=("${(@kv)reply}")
(( my_date = $parse_new[time] ))
[[ -n $parse_new[rpttime] ]] && (( new_recurring = 1 ))
if zstyle -t $context reformat-date; then
  local datefmt
  zstyle -s $context date-format datefmt ||
    datefmt="%a %b %d %H:%M:%S %Z %Y"
  strftime -s REPLY $datefmt $parse_new[time]
  addline="$REPLY $parse_new[text1]"
fi

# $calendar doesn't necessarily exist yet.

local -a match mbegin mend
local my_uid their_uid

# Match a UID, a unique identifier for the entry inherited from
# text/calendar format.
local uidpat='(|*[[:space:]])UID[[:space:]]##(#b)([[:xdigit:]]##)(|[[:space:]]*)'
if [[ $addline = ${~uidpat} ]]; then
  my_uid=${(U)match[1]}
fi

# start of subshell for OS file locking
(
# start of block for following always to clear up lockfiles.
# Not needed but harmless if OS file locking is used.
{
  if (( ! nolock )); then
    if zmodload -F zsh/system b:zsystem && zsystem supports flock &&
      zsystem flock $calendar 2>/dev/null; then
      # locked OK
      :
    else
      calendar_lockfiles $calendar || exit 1
    fi
  fi

  if [[ -f $calendar ]]; then
    calendar_read $calendar

    if [[ -n $my_uid ]]; then
      # Pre-scan to find recurring events with a UID
      for line in $calendar_entries; do
	calendar_parse $line  ||  continue
	# Recurring with a UID?
	if [[ -n $reply[rpttime] && $line = ${~uidpat} ]]; then
	  # Yes, so record this as a recurring event.
	  their_uid=${(U)match[1]}
	  recurring_uids[$their_uid]=$reply[time]
	fi
      done
    fi

    {
      for line in $calendar_entries; do
	calendar_parse $line  ||  continue
	parse_old=("${(@kv)reply}")
	if (( ! done && ${parse_old[time]} > my_date )); then
	  print -r -- $addline
	  (( done = 1 ))
	fi
	if [[ -n $parse_old[rpttime] ]]; then
	  (( old_recurring = 1 ))
	else
	  (( old_recurring = 0 ))
	fi
	if [[ -n $my_uid && $line = ${~uidpat} ]]; then
	  their_uid=${(U)match[1]}
	  if [[ $my_uid = $their_uid ]]; then
	    # Deal with recurrences, being careful in case there
	    # are one-off variants that don't replace recurrences.
	    #
	    # Bug 1: "calendar" still doesn't know about one-off variants.
	    # Bug 2: neither do I; how do we know which occurrence
	    # it replaces?
	    # Bug 3: the code for calculating recurrences is awful anyway.

	    if (( old_recurring && new_recurring )); then
	      # Replacing a recurrence; there can be only one.
	      continue
	    elif (( ! new_recurring )); then
	      # Not recurring.  See if we have previously found
	      # a recurrent version
	      [[ -n $recurring_uids[$their_uid] ]] && (( old_recurring = 1 ))
	      # No, so assume this is a straightforward replacement
	      # of a non-recurring event.
	      (( ! old_recurring )) && continue
	      # It's recurring, but if this is a one-off at the
	      # same time as the previous one, replace anyway.
	      [[ -z $parse_old[$rpttime] ]] &&
	        (( ${parse_new[time]} == ${parse_old[time]} )) &&
		continue
	    fi
	  fi
	fi
	if [[ $parse_old[time] -eq $my_date && $line = $addline ]]; then
	  (( done )) && continue # paranoia: shouldn't happen
	  (( done = 1 ))
	fi
	print -r -- $line
      done
      (( done )) || print -r -- $addline
    } >$newfile
    if (( ! nobackup )); then
      if ! mv $calendar $calendar.old; then
	print "Couldn't back up $calendar to $calendar.old.
New calendar left in $newfile." >&2
	(( rstat = 1 ))
      fi
    fi
  else
    print -r -- $line >$newfile
  fi

  if (( !rstat )) && ! mv $newfile $calendar; then
    print "Failed to rename $newfile to $calendar.
Old calendar left in $calendar.old." >&2
    (( rstat = 1 ))
  fi
} always {
  (( ${#lockfiles} )) && rm -f $lockfiles
}

exit $rstat
)