-
Notifications
You must be signed in to change notification settings - Fork 7
/
ical2org.el
262 lines (233 loc) · 9.87 KB
/
ical2org.el
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
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
;;; ics2org.el -- convert icalendar to org
;; Copyright (C) 2010, 2011 Michael Markert
;; Author: Michael Markert <[email protected]>
;; Created: 2010/12/29
;; Version: 0.3.1
;; Keywords: org, calendar
;; This file is NOT part of Emacs.
;;
;; This program is free software; you can redistribute it and/or
;; modify it under the terms of the GNU General Public License
;; as published by the Free Software Foundation; either version 2
;; of the License, or (at your option) any later version.
;;
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with this program; if not, write to the Free Software
;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
;; MA 02110-1301, USA.
;;; Commentary:
;;
;; Installation:
;;
;; (require 'ical2org)
;;
;;; Code:
(require 'icalendar)
(require 'org)
(eval-when-compile
(require 'cl))
(defconst ical2org/version "0.3.1")
(defgroup ical2org nil
"Convert iCalendar files to orgmode files."
:link '(url-link :tag "Homepage" "http://github.com/cofi/ical2org")
:group 'calendar
:prefix "ical2org/")
(defcustom ical2org/event-format
"* {SUMMARY} at {LOCATION} :{CATEGORY}:
{TIME}
{ORGANIZER}
{URL}
{DESCRIPTION}"
"String used to format an event.
Syntax is {FIELD} valid values for FIELD are: SUMMARY, LOCATION, TIME, URL,
DESCRIPTION, ORGANIZER, CATEGORY. Namely the slots of the `ical2org/event'
struct (capitalized)."
:type '(string))
(defcustom ical2org/category-separator ":"
"String used to separate multiple categories."
:type '(string))
(defcustom ical2org/completing-read #'ido-completing-read
"Function used for completing read.
Has to be compatible to `completing-read'."
:type '(function))
(defun ical2org/convert-file (fname outfile &optional nosave)
"Convert ical events from file `FNAME' to `OUTFILE' and save when `NOSAVE' is non-nil."
(interactive "fFile to convert: \nFSave as: \nP")
(let ((events
(with-temp-buffer
(insert-file-contents (expand-file-name fname))
(ical2org/import-buffer (current-buffer)))))
(save-current-buffer
(find-file outfile)
(goto-char (point-max))
(newline)
(dolist (e events)
(insert (ical2org/format e))
(newline))
(unless nosave
(save-buffer)))))
(defun ical2org/import-to-agenda (fname &optional nosave)
"Import ical events from file `FNAME' to agenda file (will be prompted).
Saves when `NOSAVE' is non-nil."
(interactive "fFile to import: \nP")
(let ((agenda-file (funcall ical2org/completing-read
"Agenda file: "
(org-agenda-files)))
(events
(with-temp-buffer
(insert-file-contents (expand-file-name fname))
(ical2org/import-buffer (current-buffer)))))
(save-current-buffer
(find-file agenda-file)
(goto-char (point-max))
(newline)
(dolist (e events)
(insert (ical2org/format e))
(newline))
(unless nosave
(save-buffer)))))
(defun ical2org/buffer-to-buffer (in out)
"Convert ical events from buffer `IN' to buffer `OUT'."
(interactive "bIn: \nBOut: ")
(save-current-buffer
(let ((events (ical2org/import-buffer in)))
(set-buffer (generate-new-buffer out))
(dolist (e events)
(insert (ical2org/format e))
(newline))
(set-window-buffer nil out)
(org-mode))))
;; private
;; output formatting
(defun ical2org/format (event)
"Replace formatstrings with slots of `EVENT'."
(replace-regexp-in-string "{.*?}"
(lambda (z)
(cdr (assoc z
`(("{SUMMARY}" . ,(ical2org/event-summary event))
("{LOCATION}" . ,(ical2org/event-location event))
("{TIME}" . ,(ical2org/event-org-timestr event))
("{URL}" . ,(ical2org/event-url event))
("{DESCRIPTION}" . ,(ical2org/event-description event))
("{ORGANIZER}" . ,(ical2org/event-organizer event))
("{CATEGORY}" . ,(mapconcat 'identity
(ical2org/event-category event)
ical2org/category-separator)))
)))
ical2org/event-format
t t))
(defun ical2org/org-recurrent (event start-decoded start-time end-time)
"Wrap `icalendar--convert-recurring-to-diary' diary in an org timestamp."
(format "<%s>"
(icalendar--convert-recurring-to-diary event start-decoded
start-time end-time)))
(defun ical2org/org-timestamp (start end)
"Format `START' and `END' as `org-time-stamp'."
(let ((start-time (nth 2 start))
(end-time (nth 2 end))
(start (car start))
(end (car end)))
(if end
(format "%s--%s" (ical2org/org-time-fmt start start-time)
(ical2org/org-time-fmt end end-time))
(if start
(ical2org/org-time-fmt start start-time)))))
(defun ical2org/org-time-fmt (time &optional with-hm)
"Format `TIME' as `org-time-stamp', if `WITH-HM' is non-nil included hh:mm.
`TIME' is an decoded time as returned from `decode-time'."
(let ((fmt (if with-hm
(cdr org-time-stamp-formats)
(car org-time-stamp-formats)))
(encoded-time (apply 'encode-time time)))
(format-time-string fmt encoded-time)))
;; entry processing
(defstruct ical2org/event
(summary "")
(location "")
(org-timestr "")
(url "")
(description "")
(organizer "")
(category '()))
(defun ics2org/datetime (property event zone-map)
"Return datetime values for `PROPERTY' of `EVENT' with `ZONE-MAP'.
Return a triple of (decoded isodate time).
Where `decoded' is a decoded datetime,
`isodate' a date as yy mm dd string,
`time' a time as hh:mm string."
(let* ((dt (icalendar--get-event-property event property))
(zone (icalendar--find-time-zone
(icalendar--get-event-property-attributes event property) zone-map))
(decoded (icalendar--decode-isodatetime dt nil zone-map)))
(list decoded
(icalendar--datetime-to-iso-date decoded)
(ignore-errors
(icalendar--datetime-to-colontime decoded)))))
(defun ical2org/get-property (event property &optional default clean)
"Return `PROPERTY' of `EVENT' or `DEFAULT'."
(let ((prop (or (icalendar--get-event-property event property)
default)))
(if clean
(icalendar--convert-string-for-import prop)
prop)))
(defun ical2org/get-org-timestr (event zone-map)
"Return org-timestring for `EVENT' with `ZONE-MAP'."
(let* ((start (ics2org/datetime 'DTSTART event zone-map))
(start-day (nth 1 start))
(start-time (nth 2 start))
(end (ics2org/datetime 'DTEND event zone-map))
(end-day (or (nth 1 end) start-day))
(end-time (or (nth 2 end) start-time))
(rrule (icalendar--get-event-property event 'RRULE))
(rdate (icalendar--get-event-property event 'RDATE))
(duration (icalendar--get-event-property event 'DURATION)))
(when duration
(let ((new-end (icalendar--add-decoded-times
(car start)
(icalendar--decode-isoduration duration))))
(setq end-day (icalendar--datetime-to-iso-date new-end))
(setq end-time (icalendar--datetime-to-colontime new-end))
(setq end (list new-end end-day end-time))))
(cond
(rrule (ical2org/org-recurrent event (car start) start-time end-time))
(t (ical2org/org-timestamp start end)))))
(defun ical2org/extract-event (ical-event zone-map)
"Extracts `ical2org/event' from `ICAL-EVENT' using the timezone map `ZONE-MAP'."
(let ((summary (ical2org/get-property ical-event 'SUMMARY "" t))
(location (ical2org/get-property ical-event 'LOCATION "" t))
(org-timestr (ical2org/get-org-timestr ical-event zone-map))
(url (ical2org/get-property ical-event 'URL ""))
(description (ical2org/get-property ical-event 'DESCRIPTION "" t))
(organizer (ical2org/get-property ical-event 'ORGANIZER "" t))
(category (split-string (ical2org/get-property ical-event 'CATEGORIES "" t)
"," t)))
(make-ical2org/event :summary summary
:location location
:org-timestr org-timestr
:url url
:description description
:organizer organizer
:category category)))
(defun ical2org/import-elements (ical-elements)
"Collects events from `ICAL-ELEMENTS' into a list of `ical2org/event's."
(let ((events (icalendar--all-events ical-elements))
(zone-map (icalendar--convert-all-timezones ical-elements)))
(loop for event in events
collect (ical2org/extract-event event zone-map))))
(defun ical2org/import-buffer (buffer)
"Return all events in icalendar `BUFFER' as `ical2org/event's."
(save-current-buffer
(set-buffer (icalendar--get-unfolded-buffer buffer))
(goto-char (point-min))
(if (re-search-forward "^BEGIN:VCALENDAR\\s-*$" nil t)
(progn
(beginning-of-line)
(ical2org/import-elements (icalendar--read-element nil nil)))
(message "Buffer does not contain icalendar contents!"))))
(provide 'ical2org)
;;; ical2org.el ends here