summaryrefslogtreecommitdiffstats
path: root/emacsconf-update.el
diff options
context:
space:
mode:
authorSacha Chua <sacha@sachachua.com>2021-11-13 00:21:33 -0500
committerSacha Chua <sacha@sachachua.com>2021-11-13 00:22:42 -0500
commited5b0d0410085384651124b1438b6a70d29bbc92 (patch)
tree56e53d51d4d60df334bf033cc8a160cf880cf075 /emacsconf-update.el
parent4856e857784f483f0442adb41618ee9a9483f32d (diff)
downloademacsconf-el-ed5b0d0410085384651124b1438b6a70d29bbc92.tar.xz
emacsconf-el-ed5b0d0410085384651124b1438b6a70d29bbc92.zip
Initial version
Diffstat (limited to 'emacsconf-update.el')
-rw-r--r--emacsconf-update.el159
1 files changed, 159 insertions, 0 deletions
diff --git a/emacsconf-update.el b/emacsconf-update.el
new file mode 100644
index 0000000..55c29c7
--- /dev/null
+++ b/emacsconf-update.el
@@ -0,0 +1,159 @@
+;;; emacsconf-update.el --- Update an Org file with the current schedule -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2021 Sacha Chua
+
+;; Author: Sacha Chua <sacha@sachachua.com>
+;; Keywords: convenience, calendar
+;; Version: 0.1
+;; URL: https://github.com/emacsconf/emacsconf-el
+;;
+;; 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 3 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, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; The following code should retrieve the conference schedule and update
+;; a file that people use for notes. It should keep the notes people
+;; already have, and just update the scheduled times or create new notes
+;; as needed. If the calendar file has UID properties, it is used to
+;; match headings. If not, headings are used.
+
+;; Make a file for your notes with Org, and then call
+;; =M-x emacsconf-update-org-from-ical-url= . It should load the talks and
+;; tentative schedules from the wiki. You can call it again and it should
+;; update the titles and schedules while still keeping whatever notes
+;; you've added.
+;;
+;; You may also like =M-x emacsconf-update-view-org-agenda=.
+;;
+;;; Code:
+
+(defgroup emacsconf-update nil
+ "Convenience functions for downloading the schedule and updating an Org file."
+ :group 'communication)
+
+(defcustom emacsconf-update-ical-url "https://emacsconf.org/current/emacsconf.ics"
+ "URL to get schedule updates from."
+ :type 'string
+ :group 'emacsconf-update)
+
+(defcustom emacsconf-update-create-entries t
+ "Non-nil means create missing entries."
+ :type '(choice (const t "Create missing entries")
+ (const nil "Don't create missing entries"))
+ :group 'emacsconf-update)
+
+(require 'icalendar)
+(require 'ox-icalendar)
+
+(defun emacsconf-update-org-from-ical-url ()
+ "Update current notes from iCalendar file at `emacsconf-update-ical-url'."
+ (interactive)
+ (emacsconf-update-org-from-ical-string
+ (with-temp-buffer
+ (url-insert-file-contents emacsconf-update-ical-url)
+ (buffer-string))))
+
+(defun emacsconf-update-org-from-ical-file (filename)
+ "Prompt for FILENAME and update current notes from schedule."
+ (interactive "FFilename: ")
+ (emacsconf-update-org-from-ical-string (with-temp-buffer (insert-file-contents filename) (buffer-string))))
+
+(defun emacsconf-ical-get-calendar-from-string (string)
+ "Read the calendar data from STRING."
+ (with-temp-buffer
+ (insert string)
+ (goto-char (point-min))
+ (with-current-buffer (icalendar--get-unfolded-buffer (current-buffer))
+ (goto-char (point-min))
+ (when (re-search-forward "^BEGIN:VCALENDAR\\s-*$" nil t)
+ (beginning-of-line)
+ (icalendar--read-element nil nil)))))
+
+(defun emacsconf-update--get-time-from-event (event property zone-map)
+ "Return time of EVENT in PROPERTY with possible conversion from ZONE-MAP."
+ (let* ((dt (icalendar--get-event-property event property))
+ (tz (icalendar--find-time-zone (icalendar--get-event-property-attributes event property) zone-map)))
+ (apply 'encode-time (icalendar--decode-isodatetime dt nil tz))))
+
+(defun emacsconf-update--format-new-entry (event zone-map)
+ "Return string for new entry for EVENT."
+ (let* ((dtstart-time (emacsconf-update--get-time-from-event event 'DTSTART zone-map))
+ (dtend-time (emacsconf-update--get-time-from-event event 'DTEND zone-map)))
+ (format "\n* %s\n:PROPERTIES:\n:UUID: %s\n:URL: %s\n:END:\nTentative schedule: %s--%s\n\n"
+ (icalendar--convert-string-for-import
+ (icalendar--get-event-property event 'SUMMARY))
+ (icalendar--get-event-property event 'UID)
+ (icalendar--get-event-property event 'URL)
+ (org-format-time-string (cdr org-time-stamp-formats) dtstart-time)
+ (org-format-time-string (cdr org-time-stamp-formats) dtend-time))))
+
+(defun emacsconf-update--update-entry (event zone-map)
+ "Update the current Org heading to match EVENT.
+Use ZONE-MAP for timestamp decoding."
+ (let* ((dtstart-time (emacsconf-update--get-time-from-event e 'DTSTART zone-map))
+ (dtend-time (emacsconf-update--get-time-from-event e 'DTEND zone-map)))
+ (org-back-to-heading)
+ (when (looking-at org-complex-heading-regexp)
+ (replace-match (icalendar--convert-string-for-import
+ (icalendar--get-event-property e 'SUMMARY))
+ t t nil 4))
+ (org-end-of-meta-data)
+ (when (re-search-forward (concat "Tentative schedule: " org-ts-regexp "--" org-ts-regexp " *\n")
+ nil
+ (save-excursion (org-end-of-subtree)))
+ (replace-match ""))
+ (insert "Tentative schedule: "
+ (org-format-time-string (cdr org-time-stamp-formats)
+ dtstart-time)
+ "--"
+ (org-format-time-string (cdr org-time-stamp-formats)
+ dtend-time)
+ "\n")))
+
+(defun emacsconf-update-org-from-ical-string (string)
+ "Update Org headings in current file by using STRING."
+ (save-excursion
+ (let* ((ical (emacsconf-ical-get-calendar-from-string string))
+ (zone-map (icalendar--convert-all-timezones ical)))
+ (mapc (lambda (e)
+ (let ((pos (org-find-property "UUID" (icalendar--get-event-property e 'UID))))
+ (cond
+ (pos
+ (goto-char pos)
+ (emacsconf-update--update-entry e zone-map))
+ (emacsconf-update-create-entries
+ (goto-char (point-max))
+ (insert (emacsconf-update--format-new-entry e zone-map))))))
+ (icalendar--all-events ical)))))
+
+;;;###autoload
+(defun emacsconf-update-view-org-agenda ()
+ "Display the agenda."
+ (interactive)
+ (let (times
+ min-time
+ max-time
+ (org-agenda-files (list (buffer-file-name))))
+ ;; Collect all the times
+ (save-excursion
+ (goto-char (point-min))
+ (while (re-search-forward org-ts-regexp nil t)
+ (setq times (cons (time-to-days (encode-time (org-parse-time-string (match-string 0)))) times)))
+ (setq min-time (apply 'min times) max-time (apply 'max times)))
+ (org-agenda-list nil
+ min-time
+ (1+ (- max-time min-time)))))
+
+(provide 'emacsconf-update)
+;;; emacsconf-update.el ends here