;;; linkfarm-mode --- mode for maintaining consistent links across a website
;; This is gross, and disgusting, and many other things besides. It's
;; what I use to maintain the links on my website in a roughly
;; consistent fashion. It's not automatic, because I'm too lazy to
;; figure out how to do that in a way that doesn't kill performance;
;; what it does is when triggered, it tries to find appropriate
;; linkage for the word ahead of point. It patches relative URLs where
;; necessary, and generally tries to do the right thing.
;; You'll need linker.pl to support this; it should be available from
;; wherever you got this file. Hopefully at
;; http://www.waider.ie/hacks/emacs/
;; Suggested invocation:
;; (add-hook 'html-mode-hook 'linkfarm-mode)
;; or whatever. This ain't VB.
;; Things you may wish to mess with.
(defvar linkfarm-filename (expand-file-name "~/public_html/.linkfarm"))
(defvar linkfarm-web-dir (expand-file-name "~/public_html/"))
(defvar linkfarm-exe (expand-file-name "~/src/perl/linker.pl"))
;; Things I'd rather you didn't mess with, since they're for internal
;; use.
(defvar linkfarm-load-time nil)
(defvar linkfarm-alist nil)
;;; Why do I have to write crap like this?
(defun linkfarm-newer(t1 t2)
"Compare two times, return T1 newer than T2.
Doesn't deal with the miniscule third component, handles nil values."
(if t1 (if t2 (if (> (nth 0 t1) (nth 0 t2)) t
(if (= (nth 0 t1) (nth 0 t2))
(if (> (nth 1 t1) (nth 1 t2)) t nil)
nil)) t) nil))
;;; Load the linkfarm if necessary (or forced)
(defun linkfarm-load( &optional force )
"(Re)Load the linkfarm file, optionally using FORCE.
Checks the file date vs. last read time if you don't force it."
(let ((buf (get-buffer-create " *linkfarm*")))
(save-excursion
(set-buffer buf)
(goto-char (point-min))
(if (or force (eobp))
(setq linkfarm-load-time nil))
(if (linkfarm-newer linkfarm-load-time
(nth 5 (file-attributes linkfarm-filename))) ()
(setq linkfarm-alist nil)
(erase-buffer)
(message "Loading linkfarm...")
(call-process
(concat (expand-file-name linkfarm-exe)) nil buf nil "-e")
(setq linkfarm-load-time (current-time))
(message "Loading linkfarm...done")
(goto-char (point-min))
(message "Parsing linkfarm...")
(setq linkfarm-alist (read buf))
(message "Parsing linkfarm...done")))))
;; Check the word preceeding point for a match in the linkfarm.
;; FIXME:
;; filter text from current page as per the linker.pl script
;; (i.e. collapse whitespace)
;; match only on word boundaries
(defun linkfarm-check-words ()
"Try to find an appropriate link for the word(s) before point."
(delete nil (mapcar (function
(lambda( linkdata )
(let* ((linkwords (car linkdata))
(linkurls (cdr linkdata))
(cur-point (point))
(backtrack (length linkwords))
(checkwords (buffer-substring
(if (>= backtrack cur-point) 1
(- cur-point backtrack))
cur-point))
(case-fold-search t))
(if (string-match (regexp-quote linkwords)
checkwords)
linkdata
nil)))) linkfarm-alist)))
;; this is the actual command to call the above.
(defun linkfarm-check-words-quietly ()
"Command to check for linkable words."
(interactive)
(linkfarm-load) ;; refresh linkwords list
(let* ((linklist (linkfarm-check-words))
(linkitem (car linklist))
(linklength (length (car linkitem))))
(if (> (length linklist) 0)
;; find the longest link
(mapcar (function (lambda( li )
(if (> (length (car li)) linklength)
(setq linkitem li
linklength (length (car li))))))
linklist))
(if linkitem
(let* ((linkword (car linkitem))
(backtrack (length linkword))
(url (nth 1 linkitem)))
;; patch the URL if it's relative.
(if (string-match "^\\w+:" url)
() ;; looks absolute, bugger it.
(let ((here buffer-file-name))
(if (or (null here)
(not (string-match (concat "^" linkfarm-web-dir) here)))
(progn
(message (if (null here)
"No filename to work with!"
(concat here " not in web tree!")))
(sit-for 2))
(setq here (file-name-directory
(substring here (length linkfarm-web-dir)))
url (file-relative-name url here)))))
(save-excursion
(forward-char (- backtrack))
(insert (concat "")))
(insert "")))))
;; much faster version, without substring matching
(defun linkfarm-frob-word (word)
(and (try-completion word linkfarm-alist)
(let ((url (nth 1 (assoc word linkfarm-alist))))
(concat "" word ""))))
;; Hacked self-insert-command which calls the above command before
;; inserting.
(defun linkfarm-self-insert-command(n)
(interactive "p")
(linkfarm-check-words-quietly)
(self-insert-command n))
;; Hook function for file-writing
(defun linkfarm-update()
"Hook function (optionally a command) to update the linkfarm.
Only updates if it can find a buffer-file-name and that
buffer-file-name is in the linkfarm-web-dir (which see)."
(interactive)
(if (and (buffer-file-name)
(string-match (concat "^" linkfarm-web-dir) (buffer-file-name)))
(progn
(message "Updating linkfarm...")
(shell-command (concat (expand-file-name linkfarm-exe) " -q "
(buffer-file-name)))
(message "Updating linkfarm...done.")))
;; return nil so that the file will actually get written. Yes, I had
;; to RTFM.
nil)
;; Not really a mode, but gives you a focal point...
;;;###autoload
(defun linkfarm-mode ()
"Define the linkfarm keymap, such as it is."
(interactive)
(let ((keymap (copy-keymap (current-local-map))))
(define-key keymap "\C-c?" 'linkfarm-check-words-quietly)
;; uncommenting the following might /just/ work if you have some sort
;; of supercomputer once your linkfarm file gets any way big.
;; (substitute-key-definition 'self-insert-command
;; 'linkfarm-self-insert-command
;; keymap global-map)
;; this is slightly less harsh, but still hurts.
;; (define-key keymap " " 'linkfarm-self-insert-command)
(use-local-map keymap)
(add-hook 'local-write-file-hooks 'linkfarm-update)))