Emacs Calendar Extension – Information Unity (Part 2)

0

Go back to the first post in this series, I was trying to solve the problem of having many views of my calendars, but only a single source of record. Specifically, I wanted to be able to view this “source” calendar in Emacs without the manual syncing process.

As was discussed earlier, a few of the issues were solved:

  1. Google Calendar exports calendars in the iCalendar format
  2. Emacs Calendar is able to import an iCalendar format file via URL

Stellar! This is going to be easy! I’ll just list out my calendars’ iCalendar URLs (provided by Google), and have Emacs Calendar cycle through them and import them. Nice :)

First, let’s create a var that points to our diary file (that way we can export it to our site config if necessary)

1
(setq diary-file (concat my-emacs-org "diary_entries"))

Next, I’ll create an alist  to hold all my calendars.

1
2
3
(setq my-ical-calendars-alist '(
 ("test", "https://www.google.com/calendar/icaltest.ics")
 ))

All nice and easy thus far. The next thing we need to do is write a nifty function to help import the URL as icalender.el can only import from a file via icalendar-import-file.

No problem, as there’s a built-in function to download a URL to a local temp copy - url-file-local-copy.

We’ll write our function to:

  1. Download the URL
  2. Pass the temp file to icalendar-import-file
  3. Close the buffer
1
2
3
4
5
6
7
(defun import-remote-ical (url my-diary-file)
 "Download ics file from URL and add to diary"
 (let ((tmpfile (url-file-local-copy url)))
 (icalendar-import-file tmpfile my-diary-file nil)
 (kill-buffer (car (last (split-string tmpfile "/"))))
 )
)

I run it and am astounded that it works! So I run it a few more times to make sure I’m not imagining things. Yup – it does work!

I hurriedly open my diary file, anxious to see my handiwork and… (wait for it) … uggh. There are multiple duplicates for each event. I should’ve known that it couldn’t of been that easy.

But hey, no problem. I manipulate text like a mad man – this is nothing.

Me being newbish to Lisp I immediately start googling for a means to deal with this. I know how to easily do this type of work in a bevy of other languages, but lacked a clue as to the conventional means in Emacs Lisp.

After a relatively short search, I found (by combining a few topic results) an acceptable solution:

NOTE: As previously stated, I’m a Lisp newb. If my code sucks help me out and give me some constructive criticism!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
(defun remove-calendar-duplicates (my-diary-file)
  "Will remove all duplicate entries from the specified calender / diary file.
 
The argument is a full path to said diary file."
  ;;loads the selected diary file
  (find-file my-diary-file)
 
  ;;turns the buffer contents into a list
  (setq buf-str-list (split-string (buffer-string) "\n"))
 
  ;;deletes exact duplicates in the list
  (delete-dups buf-str-list)
 
  ;;clears the buffer and move to point-min
  (erase-buffer)
  (goto-char (point-min))
 
  ;;insert each line into the buffer
  ;;mapconcat runs the func (1st arg) over the list, returning a single string
  ;;identity is a built-in that returns the value unchanged
  (insert (mapconcat 'identity buf-str-list "\n"))
  )

As you can see from the code comments, it’s pretty straightforward.

  • Load the diary file into the buffer
  • Add each line of the buffer into a list
  • De-duplicate the items in the list with delete-dups
  • Clear the buffer
  • Go to the first position in the buffer
  • Concat my list into a newline separated string into the buffer

Not bad – only 6 lines of code (minus the comments, of course).

The final piece of this (at least for a single calendar – more on automating all the calendars in a bit) is that I wanted a minor format change for birthday entries. Specifically, appending the Emacs Calendar “%d” format placeholder will take an anniversary entry and automatically show the numerical result.

For example:

%%(and (diary-anniversary 3 9 1973))  My Birthday %d

Would show:

My Birthday (40)

Very handy!

Off to do more research I went… I needed to

  1. Load the diary file
  2. Cycle through each entry looking for “birthday”
  3. Append “%d” to that line

Again, easy to do in languages I know – but Lisp is a little different. After Frankensteining a few posts together with my own knowledge I came up with the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
(defun add-ages-to-birthdays (my-diary-file)
  "Will modify the diary entries that have 'birthday' in the entry and add the %d modifier"
  (find-file my-diary-file)
  (goto-char (point-min))
  (while (= 0 (forward-line)) 
    (progn
      (setq p1 (line-beginning-position) )
      (setq p2 (line-end-position) )
      (setq myLine (buffer-substring-no-properties p1 p2))
      (when (and (string-match "birthday" myLine) (not (string-match "%d" myLine)))
	(progn
	  (goto-char (line-beginning-position))
	  (kill-line)
	  (insert (concat myLine " %d"))
	  )
	)
      )	
    )	  
  )

Actually, it was pretty simple once I learned the functions for interacting with the buffer. As I’ll continue to state, there is probably a much better LISP way to do this that’s currently beyond my knowledge – please feel free to comment and let me know!

As for now I’ll wrap up this post, it’s a little lengthy. We’ve covered getting all of my Google calendars – de-duplcated and formatted – into my emacs diray file. But we’ve only covered the manual way of doing it. Hell – we can’t even M-x what we have so far – we’d have to eval-region or eval-buffer our import-remote-ical function to get it to work – and that sucks!

I will follow up with the final post in the series shortly in which we’ll cover:

  • Automatically importing all my calendars
  • Interactive means of updating a calendar

Until then – thanks for reading!

Full Codegcal_sync.el

Tags: , , ,

Reply

%d bloggers like this: