Emacs Chat: Harry R. Schwartz

We talk about Emacs NYC, organizing your configuration, pair programming and more.

TRANSCRIPT HERE!

Literate programming and my Emacs configuration file

Update 2013-08-30: Changed Emacs Starter Kit link. Thanks, Thomas!

Inspired by the Emacs Starter Kit and the literate programming features in org-babel, I reviewed and organized my Emacs configuration. I’m looking forward to adding more notes to my configuration as I explore!

Literate programming is the idea that you should write first and program second, and that you can interweave the program into your explanation. Nifty.

Here’s my Emacs configuration (you can also find it at sach.ac/dotemacs. Share yours! =)

Using org2blog to publish Org-mode subtrees

This patch modifies punchagan’s org2blog to allow you to publish an Org subtree with M-x org2blog-post-subtree. It posts a draft by default, and publishes the post if you call it with C-u M-x org2blog-post-subtree. It gets the posting date from SCHEDULED, DEADLINE, active or inactive timestamps, or the Post Date property, and lets you use tags as categories or use a separate Categories property. It inherits tags from parent headings, too. It picks up the title from the subtree heading or uses the Title property.

Patch

diff --git a/org2blog.el b/org2blog.el
index dc88291..b95caba 100644
--- a/org2blog.el
+++ b/org2blog.el
@@ -77,6 +77,11 @@
   :group 'org2blog 
   :type 'string)
 
+(defcustom org2blog-use-tags-as-categories nil
+  "Non-nil means assign :tags: to WordPress categories instead."
+  :group 'org2blog
+  :type 'boolean)
+
 (defvar org2blog-categories-list nil 
   "List of weblog categories")
 
@@ -433,4 +438,143 @@
        (goto-char current-pos)
        (command-execute (lookup-key org-mode-map (kbd "C-c t")))))))
 
+(defun org2blog-create-categories (categories)
+  "Create unknown CATEGORIES."
+  (mapcar
+   (lambda (cat)
+     (if (and (not (member cat org2blog-categories-list))
+              (y-or-n-p (format "Create %s category? " cat)))
+         (wp-new-category org2blog-server-xmlrpc-url
+                          org2blog-server-userid
+                          (org2blog-password)
+                          org2blog-server-blogid
+                          cat)))
+   categories))
+
+(defun org2blog-password ()
+  "Get password or prompt if needed."
+  (or org2blog-server-pass
+      (setq org2blog-server-pass (read-passwd "Weblog password? "))))
+
+(defun org2blog-upload-images-insert-links (&optional beg end)
+  "Upload images and replace with links in the region specified by BEG to END."
+  (interactive "r")
+  (let ((re 
+        (concat "\\[\\[\\(.*\\)" 
+                (substring (org-image-file-name-regexp) 0 -2)
+                "\\]\\]"))
+       file-all-urls file-name file-web-url blog-pass)
+    (save-excursion
+      (save-restriction
+        (narrow-to-region (or beg (point-min))
+                          (or end (point-max)))
+        (goto-char (point-min))
+        (while (re-search-forward re nil t 1)
+          (setq file-name (concat 
+                           (match-string-no-properties 1)
+                           "."
+                           (match-string-no-properties 2)))
+          (unless (save-match-data (string-match org-link-types-re file-name))
+            (save-match-data 
+              (if (assoc file-name file-all-urls)
+                  (setq file-web-url (cdr (assoc file-name file-all-urls)))
+                (setq file-web-url
+                      (cdr (assoc "url" 
+                                  (metaweblog-upload-image org2blog-server-xmlrpc-url
+                                                           org2blog-server-userid
+                                                           (org2blog-password)
+                                                           org2blog-server-weblog-id
+                                                           (get-image-properties file-name))))
+                      file-all-urls (append file-all-urls (list (cons 
+                                                                 file-name file-web-url))))))
+            (replace-match (concat "[[" file-web-url "]]") t t nil 0)))))
+    file-all-urls))
+
+(defun org2blog-post-subtree (&optional publish)
+  "Post the current entry as a draft. Publish if PUBLISH is non-nil."
+  (interactive "P")
+  (let ((post (org2blog-parse-subtree))
+        post-id)
+    (org2blog-create-categories (cdr (assoc "categories" post)))
+    (setq post-id (cdr (assoc "post-id" post)))
+    (save-excursion 
+      (org2blog-upload-images-insert-links (org-back-to-heading) (org-end-of-subtree)))
+    (if post-id
+        (metaweblog-edit-post org2blog-server-xmlrpc-url
+                             org2blog-server-userid
+                              (org2blog-password)
+                             post-id
+                              post
+                             publish)
+      (setq post-id
+            (metaweblog-new-post
+             org2blog-server-xmlrpc-url
+             org2blog-server-userid
+             (org2blog-password)
+             org2blog-server-blogid
+             post
+             publish))
+      (org-entry-put (point) "Post ID" post-id)
+      (message (if publish
+                   "Published (%s): %s"
+                 "Draft (%s): %s")
+               post-id
+               (cdr (assoc "title" post))))))
+
+(defun org2blog-parse-subtree ()
+  "Parse the current subtree as a blog entry."
+  (let (html-text
+        (post-title (or (org-entry-get (point) "Title")
+                        (org-get-heading t)))
+        (post-id (org-entry-get (point) "Post ID"))
+        ;; Set post-date to the Post Date property or look for timestamp
+        (post-date (or (org-entry-get (point) "Post Date")
+                       (org-entry-get (point) "SCHEDULED")
+                       (org-entry-get (point) "DEADLINE")                       
+                       (org-entry-get (point) "TIMESTAMP_IA")
+                       (org-entry-get (point) "TIMESTAMP")))
+        (tags (org-get-tags-at (point) nil))
+        (categories (org-split-string (or (org-entry-get (point) "CATEGORIES") "") ":")))
+    ;; Convert post date to ISO timestamp
+    (setq post-date
+          (format-time-string "%Y%m%dT%T"
+                              (if post-date
+                                  (apply 'encode-time (org-parse-time-string post-date))
+                                (current-time))
+                              t))
+    (if org2blog-use-tags-as-categories
+        (setq categories tags
+              tags nil))
+    (save-excursion
+      (setq html-text
+            (org-export-region-as-html
+             (and (org-back-to-heading) (line-end-position))
+             (org-end-of-subtree)
+             t 'string))
+      (setq html-text
+            (with-temp-buffer
+              (insert html-text)
+              (goto-char (point-min))
+              ;; Fix newlines
+             (let (start-pos end-pos)
+                (setq start-pos (point-min))
+               (goto-char start-pos)
+                (while (re-search-forward "<\\(pre\\|blockquote\\).*?>" nil t 1)
+                  (setq end-pos (match-beginning 0))
+                  (replace-string "\n" " " nil start-pos end-pos)
+                  (re-search-forward (concat "</" (match-string-no-properties 1) ">") nil t 1)
+                  (setq start-pos (match-end 0))
+                  (goto-char start-pos))
+               (setq end-pos (point-max))
+               (replace-string "\n" " " nil start-pos end-pos))
+              ;; Copy the text
+              (buffer-substring-no-properties (point-min) (point-max)))))
+    (list
+     (cons "date" post-date)
+     (cons "title" post-title)
+     (cons "tags" tags)
+     (cons "categories" categories)
+     (cons "post-id" post-id)
+     (cons "description" html-text))))
+
 (provide 'org2blog)

I like using one big Org file for all of my notes so that I can search and categorize things easily.

Here is the sample code from my ~/.emacs:

(add-to-list 'load-path "~/elisp/org2blog")
(require 'org2blog)
(setq org2blog-server-url "http://sachachua.com/blog/xmlrpc.php"
      org2blog-server-user "admin"
      org2blog-server-weblog-id ""
      org2blog-use-tags-as-categories t)
(org2blog-login)

Then I can go to the entry and call M-x org2blog-post-subtree to post a draft or C-u M-x org2blog-post-subtree to publish it.

Note that the code uses whatever heading level you’re on, so if you’re under a sub-heading of the post you want to publish, use C-c C-u outline-up-heading to go up headings until you’re at the right level.

You can get the modified source code from http://github.com/sachac/org2blog . I’ve also sent a pull request upstream.

Emacs: Working with multiple source trees

As a developer, I often find myself working with multiple trees of source code. Sometimes, I’m comparing the trunk and the branches. Sometimes, I’m copying ideas from one place to another. Sometimes, I’m working on one project and an urgent defect comes in for a different project. With good development environments such as Eclipse, I’d still need to click on the project or directory in order to change my context, and then use a keyboard shortcut to find the resource in that tree.

With an awesome development environment like my customized Emacs, however, I can easily juggle multiple source trees. Here’s the macro I wrote to make setting this up much easier:

(defmacro sacha/file-cache-setup-tree (prefix shortcut directories)
  "Set up the file-cache tree for PREFIX using the keyboard SHORTCUT.
DIRECTORIES should be a list of directory names."
  `(let ((file-cache-alist nil)
	 (directories ,directories))
     (file-cache-clear-cache)
     (while directories
       (file-cache-add-directory-using-find (car directories))
       (setq directories (cdr directories)))
     (setq ,(intern (concat "sacha/file-cache-" prefix "-alist")) file-cache-alist)
     (defun ,(intern (concat "sacha/file-cache-ido-find-" prefix)) ()
       (interactive)
       (let ((file-cache-alist ,(intern (concat "sacha/file-cache-" prefix "-alist"))))
	 (call-interactively 'file-cache-ido-find-file)))
     (global-set-key (kbd ,shortcut)
		     (quote ,(intern (concat "sacha/file-cache-ido-find-" prefix))))))

With that, I can use the following to map C-c p to finding files in my personal directories:

(sacha/file-cache-setup-tree
 "personal"
 "C-c p"
 '("/var/www/html/drupal"
   "~/elisp"
   "~/personal"))

I’ve also set up keyboard shortcuts for the other trees I’m working on.

You’ll need file-cache, ido, and probably something like this:

(require 'filecache)
(require 'ido)
(defun file-cache-ido-find-file (file)
  "Using ido, interactively open file from file cache'.
First select a file, matched using ido-switch-buffer against the contents
in `file-cache-alist'. If the file exist in more than one
directory, select directory. Lastly the file is opened."
  (interactive (list (file-cache-ido-read "File: "
                                          (mapcar
                                           (lambda (x)
                                             (car x))
                                           file-cache-alist))))
  (let* ((record (assoc file file-cache-alist)))
    (find-file
     (expand-file-name
      file
      (if (= (length record) 2)
          (car (cdr record))
        (file-cache-ido-read
         (format "Find %s in dir: " file) (cdr record)))))))

(defun file-cache-ido-read (prompt choices)
  (let ((ido-make-buffer-list-hook
	 (lambda ()
	   (setq ido-temp-list choices))))
    (ido-read-buffer prompt)))
(add-to-list 'file-cache-filter-regexps "docs/html")
(add-to-list 'file-cache-filter-regexps "\\.svn-base$")
(add-to-list 'file-cache-filter-regexps "\\.dump$")

Emacs w3m: Open pages in external browsers

Sometimes w3m is not enough. To make it easier to open the current page in a browser such as Mozilla Firefox, add the following to your ~/.emacs:

(defun wicked/w3m-open-current-page-in-firefox ()
  "Open the current URL in Mozilla Firefox."
  (interactive)
  (browse-url-firefox w3m-current-url)) ;; (1)

(defun wicked/w3m-open-link-or-image-in-firefox ()
  "Open the current link or image in Firefox."
  (interactive)
  (browse-url-firefox (or (w3m-anchor) ;; (2)
                          (w3m-image)))) ;; (3)

This defines a function that uses the current URL being browsed(1) and another function that takes the URL of the link at point(2). If no link is found, it takes the URL of the image at point(3).

You can use other browse-url functions instead of browse-url-firefox. For example, replacing browse-url-firefox with browse-url-kde will open the page, link, or image in Konqueror, KDE’s web browser.

I like binding f to the function that opens the current URL in Mozilla Firefox and F to the function that opens the current link or image in Mozilla Firefox. To do the same, add the following to your ~/.emacs:

(eval-after-load 'w3m
  (progn 
    (define-key w3m-mode-map "f" 'wicked/w3m-open-current-page-in-firefox)
    (define-key w3m-mode-map "F" 'wicked/w3m-open-link-or-image-in-firefox)))


This is part of the book that I’m writing about Emacs, which will be published by No Starch Press if I manage to get it together in time.

Emacs and w3m: Making tabbed browsing easier

If you browse with a lot of open tabs, like I do, w3m will be much easier to use once you remap w3m-next-buffer and w3m-previous-buffer onto single-key shortcuts, allowing you to press a key to quickly flip between tabs.

By default, w3m-previous-buffer is mapped to C-c C-p and w3m-next-buffer is mapped to C-c C-n. On a QWERTY keyboard, you may want to remap w3m-previous-buffer to q and w3m-next-buffer to w. You’ll probably also want to remap w3m-close-window (which had been bound to q), and x is a good keybinding for that. To make all these changes, add the following to your ~/.emacs:

(eval-after-load 'w3m
  '(progn
     (define-key w3m-mode-map "q" 'w3m-previous-buffer)
     (define-key w3m-mode-map "w" 'w3m-next-buffer)
     (define-key w3m-mode-map "x" 'w3m-close-window)))

If you use a Dvorak keyboard layout, you can bind . to w3m-previous-buffer and , to w3m-next-buffer instead. Just add the following code to your ~/.emacs:

(eval-after-load 'w3m
  '(progn
     (define-key w3m-mode-map "." 'w3m-previous-buffer)
     (define-key w3m-mode-map "," 'w3m-next-buffer)))

(This is part of the draft for my book on Emacs, to be published by No Starch Press if I’m not too late.)