Comic Reader

To use this comics reader, first save the code that follows into a file called comics.el. Make sure that it is in your load-path. Then add a

  (require 'comics)
line to your .emacs. To read comics you need to first start up calendar with M-x calendar. Now go to the date of choice and press "c". You will be prompted for a comic name and the comic will be downloaded and displayed. The "C" key will show you all the comics in the comics-list variable.

By default, the xv program is used to show the images. To use a different image viewer, the comics-image-viewer variable needs to be set appropriately.

;;; Interface to getting comics
;;;
;;; This works with:
;;;    1. http://www.ucomics.com
;;;    2. http://www.unitedmedia.com
;;;

(eval-when-compile (require 'cl))
(require 'calendar)

(defvar comics-alist
  '(;; ucomics.com comics
    ("born lucky" "bornlucky" comics-ucomics-url-generator)
    ("cathy" "cathy" comics-ucomics-url-generator)
    ("doonesbury" "doonesbury" comics-ucomics-url-generator)
    ("duplex" "duplex" comics-ucomics-url-generator)
    ("foxtrot" "foxtrot" comics-ucomics-url-generator)
    ("garfield" "garfield" comics-ucomics-url-generator)
    ("non sequitur" "nonsequitur" comics-ucomics-url-generator)
    ("ziggy" "ziggy" comics-ucomics-url-generator)
    ;; unitedmedia.com comics
    ("born loser" "bornloser" comics-unitedmedia-url-generator)
    ("dilbert" "dilbert" comics-unitedmedia-url-generator)
    ("for better or for worse" "forbetter" comics-unitedmedia-url-generator)
    ("nancy" "nancy" comics-unitedmedia-url-generator)
    ("peanuts" "peanuts" comics-unitedmedia-url-generator)
    ("marmaduke" "marmaduke" comics-unitedmedia-url-generator))
  "Location info of comics.

Each element of the alist contains the directory that has the comic and a
function which tells us how to find the web address that needs to be fetched.")

(defvar comics-list '("cathy" "doonesbury" "dilbert" "for better or for worse"
                      "non sequitur")
  "List of comics to get with `comics-display-all'")

(defvar comics-image-viewer "xv")

(defvar comics-local-cache "~/.comics")

(defvar comics-image-url-regexp-format-string
  "<img src=\"\\([^>]*%02d[0-9]*%02d[0-9]*\.\\(gif\\|jpg\\)\\)\"")

(defvar comics-history ())

;;; http://www.ucomics.com interface
(defun comics-ucomics-url-generator (comic date &optional relative-url)
  "Generate the html page that has the COMIC for DATE."
  (cond ((eq relative-url nil)
         (format "http://www.ucomics.com/%s/%s/%02d/%02d/"
                 comic (caddr date) (car date) (cadr date)))
   ((listp relative-url)
    (apply #'format "<img src=\"\\([^>\"]*%02d%02d.gif\\)"
           (cadr relative-url)))
   (t relative-url)))

;;; http://www.unitedmedia.com interface
(defun comics-unitedmedia-url-generator (comic date &optional relative-url)
  "Generate the html page that has the COMIC for DATE."
  (cond
   ((eq relative-url nil)
    (format "http://www.unitedmedia.com/comics/%s/archive/%s-%s%02d%02d.html"
            comic comic (caddr date) (car date) (cadr date)))
   ((listp relative-url)
    (format "<img src=\"\\([^>\"]*%s[0-9]+.\\(gif\\|jpg\\)\\)\""
            (car relative-url)))
   (t (format "http://www.unitedmedia.com/%s" relative-url))))

;;; Common code
(defvar comics-image-file)
(defvar comics-temp-file)
(defvar comics-regexp)
(defvar comics-generator)

(defun comics-fetch-and-display (comic date)
  "Fetch COMIC for DATE and display it.
COMIC is only fetched if it isn't present in cache already. Otherwose the
cached copy is used."
  (let* ((elem (assoc-string comic comics-alist))
         (base (cadr elem))
         (generator (caddr elem))
         image-url cached-image)
    (unless (stringp base)
      (error "Comic %s not found" comic))
    ;; Compute cached image name...
    (setq cached-image
          (expand-file-name (apply #'format "%s!%04d!%02d!%02d" base date)
                            comics-local-cache))
    ;; If cached image exists, display it and exit.
    (if (file-exists-p cached-image)
        (comics-show-image cached-image)
      ;; Otherwise start the fetching process and set things up to display it
      ;; when the comic has been downloaded.
      (let ((tmp-buf (get-buffer-create (generate-new-buffer-name "*comic*")))
            (case-fold-search t)
            (tmp-file (make-temp-file "_comics"))
            (regexp (funcall generator nil nil (list base date))))
        (save-excursion
          (set-buffer tmp-buf)
          (set (make-local-variable 'comics-image-file) cached-image)
          (set (make-local-variable 'comics-temp-file) tmp-file)
          (set (make-local-variable 'comics-regexp) regexp)
          (set (make-local-variable 'comics-generator) generator)
          (set-process-sentinel
           (start-process "*wget*" tmp-buf "wget"
                          (funcall generator base date) "-O" tmp-file)
           'comics-html-download-sentinel))))))

(defun comics-show-image (image)
  "Show IMAGE with an external viewer."
  (start-process "*comic-displayer*" nil comics-image-viewer image))

(defun comics-html-download-sentinel (process change)
  "Sentinel to parse the html fetched by wget.
PROCESS is the wget process and CHANGE is the state when this function is
called. We will wait for wget to finish and then scan the resulting file for
the actual comic URL."
  (when (eq (process-status process) 'exit)
    (save-excursion
      (set-buffer (process-buffer process))
      (let ((tmp-file comics-temp-file)
            (regexp comics-regexp)
            (generator comics-generator)
            image-url)
        (with-temp-buffer
          (insert-file-contents-literally tmp-file)
          (goto-char (point-min))
          (when (re-search-forward regexp nil t)
            (setq image-url (funcall generator nil nil (match-string 1)))))
        (if (not image-url)
            (message "Error in parsing html page")
          (ignore-errors
            (delete-file tmp-file)
            (delete-process process))
          (set-process-sentinel
           (start-process "*wget*" (current-buffer)
                          "wget" image-url "-O" comics-image-file)
           'comics-display-comic-sentinel))))))

(defun comics-display-comic-sentinel (process change)
  "Sentinel to display comic.
PROCESS is the wget process and CHANGE is ignored."
  (when (eq (process-status process) 'exit)
    (let* ((buf (process-buffer process))
           (image-file (save-excursion (set-buffer buf) comics-image-file)))
      (ignore-errors
        (delete-process process)
        (kill-buffer buf))
      (if (not (file-exists-p image-file))
            (message "Couldn't fetch image file")
          (comics-show-image image-file)))))

(defun comics-read-comic-name ()
  "Read comic name with completion and history."
  (completing-read
   (format "Comic%s: "
           (if comics-history (format " [%s]" (car comics-history)) ""))
   comics-alist nil t nil 'comics-history (car comics-history)))

(defun comics-display (comic date)
  "Display COMIC for DATE."
  (interactive (list (comics-read-comic-name) (calendar-cursor-to-date t)))
  (comics-fetch-and-display comic date))

(defun comics-display-all (date)
  "Display all comics in `comics-list' for DATE."
  (interactive (list (calendar-cursor-to-date t)))
  (dolist (comic comics-list)
    (comics-display comic date)))

;;; Add key binding to calendar-mode-map
(define-key calendar-mode-map "c" 'comics-display)
(define-key calendar-mode-map "C" 'comics-display-all)

(provide 'comics)

Satyaki Das
Last modified: Tue Nov 25 12:07:28 PST 2003