UP | HOME

Gareth's Org-Mode Config

Table of Contents

Org-Expiry

Org-expiry provides a format for putting an expiry date in the properties of a task. It also provides a function to look for any entries that have expired, and then do something to them (I move them to archive files).

I often capture tasks which would be nice to do, but if I don't do them within a day, (or a week, or a month, or whatever) then there's no point worrying about them anymore. These are marked as expiring on the appropriate date. I have a cron job which runs the expiry function in my git repo every night.

(use-package org-expiry
  :load-path "~/elisp/"
  :config
  (setq org-expiry-handler-function 'org-expiry-archive-subtree))

You can also add a best-before date to any existing org-entry using org-expiry-insert-expiry. Irritatingly however, it uses the org date form with angle-brackets instead of square ones. This means that my agenda fills up with best-before dates. So here's a version that does the same thing but with square brackets:

(defun gds/org-expire-quietly (&optional arg)
  (interactive "P")
  "Make this entry expire quietly.

The problem is that `org-expiry-insert-expiry' uses dates of the
form <1970-01-30>, which show up in the agenda. I want it to use
dates of the form [1970-01-30] which do not."
  ;; First get the <> expiry entry into the property drawer:
  (org-expiry-insert-expiry arg)
  ;; Now replace it with a [] entry:
  (org-entry-put (point) org-expiry-expiry-property-name
                 ;; We calculate the new entry from the old entry already there.
                 (let ((timestamp (org-entry-get (point) org-expiry-expiry-property-name)))
                       ;; ...in which we replace <, and > with [, and ]
                   (with-temp-buffer
                     (insert timestamp)
                     (while (search-backward "<" nil t)
                       (replace-match "[" nil t))
                     (while (search-forward ">" nil t)
                       (replace-match "]" nil t))
                     (buffer-string)))))

Set up agenda and diary files

As projects come and go, my list of agenda files changes.

(load "~/Documents/configs/emacs/org-files.el")

Agenda UI

Some tricks to make the agenda pretty. This is bound up with the Time Management stuff (clocking in and out, and time tracking) below. When viewing an agenda with lots of time info in it, sometimes you want the detail, and other times you want to just see the big picture.

(defun gds/agenda-compact ()
  "Make the org-agenda compact"
  (interactive)
  (setq org-agenda-clockreport-parameter-plist '(:link t :maxlevel 5 :compact t)))
(defun gds/agenda-noncompact ()
  "Make the org-agenda non-compact"
  (interactive)
  (setq org-agenda-clockreport-parameter-plist '(:link t :maxlevel 5)))
(gds/agenda-compact)

(setq org-agenda-custom-commands '(("n" "Agenda and all TODO's"
                                    ((agenda "")
                                     (alltodo "")))))
(add-to-list 'org-agenda-custom-commands '("p" tags "project"))

Bernt Hansen likes to see his agenda if he stops paying attention. I'm not currently using that, but I'm keeping it around just in case.

(defun gds/jump-to-org-agenda ()
  (interactive)
  (let ((buf (get-buffer "*Org Agenda*"))
        wind)
    (if buf
        (if (setq wind (get-buffer-window buf))
            (select-window wind)
          (if (called-interactively-p)
              (progn
                (select-window (display-buffer buf t t))
                (org-fit-window-to-buffer)
                ;; (org-agenda-redo)
                )
            (with-selected-window (display-buffer buf)
              (org-fit-window-to-buffer)
              ;; (org-agenda-redo)
              )))
      (call-interactively 'org-agenda-list)))
  ;;(let ((buf (get-buffer "*Calendar*")))
  ;;  (unless (get-buffer-window buf)
  ;;    (org-agenda-goto-calendar)))
  )
;; (setq gds-org-timer (run-with-idle-timer 300 t 'gds/jump-to-org-agenda))

Project Management

I'm still working out how best to manage complex projects. This is how Bernt Hansen likes it. But I'm not sure it works well for me, so I'm also playing with some more manual tag-based stuff.

Project Management Tags.

These lines allow me to mark a heading as a "project", which will have many non-project sub-tasks. It also allows me to mark a given project as the one I'm working on right now.

(setq org-tag-persistent-alist  nil)
(setq org-tags-exclude-from-inheritance (list "project" "current"))

Fancy Project Management.

Trying out some project/agenda views from here.

I'm not currently using org habits, but this will support them if I do.

(add-to-list 'org-modules 'org-habit)

The bulk of the code:

(defun bh/is-project-p ()
  "Any task with a todo keyword subtask"
  (save-restriction
    (widen)
    (let ((has-subtask)
          (subtree-end (save-excursion (org-end-of-subtree t)))
          (is-a-task (member (nth 2 (org-heading-components)) org-todo-keywords-1)))
      (save-excursion
        (forward-line 1)
        (while (and (not has-subtask)
                    (< (point) subtree-end)
                    (re-search-forward "^\*+ " subtree-end t))
          (when (member (org-get-todo-state) org-todo-keywords-1)
            (setq has-subtask t))))
      (and is-a-task has-subtask))))

(defun bh/is-project-subtree-p ()
  "Any task with a todo keyword that is in a project subtree.
Callers of this function already widen the buffer view."
  (let ((task (save-excursion (org-back-to-heading 'invisible-ok)
                              (point))))
    (save-excursion
      (bh/find-project-task)
      (if (equal (point) task)
          nil
        t))))

(defun bh/is-task-p ()
  "Any task with a todo keyword and no subtask"
  (save-restriction
    (widen)
    (let ((has-subtask)
          (subtree-end (save-excursion (org-end-of-subtree t)))
          (is-a-task (member (nth 2 (org-heading-components)) org-todo-keywords-1)))
      (save-excursion
        (forward-line 1)
        (while (and (not has-subtask)
                    (< (point) subtree-end)
                    (re-search-forward "^\*+ " subtree-end t))
          (when (member (org-get-todo-state) org-todo-keywords-1)
            (setq has-subtask t))))
      (and is-a-task (not has-subtask)))))

(defun bh/is-subproject-p ()
  "Any task which is a subtask of another project"
  (let ((is-subproject)
        (is-a-task (member (nth 2 (org-heading-components)) org-todo-keywords-1)))
    (save-excursion
      (while (and (not is-subproject) (org-up-heading-safe))
        (when (member (nth 2 (org-heading-components)) org-todo-keywords-1)
          (setq is-subproject t))))
    (and is-a-task is-subproject)))

(defun bh/list-sublevels-for-projects-indented ()
  "Set org-tags-match-list-sublevels so when restricted to a subtree we list all subtasks.
  This is normally used by skipping functions where this variable is already local to the agenda."
  (if (marker-buffer org-agenda-restrict-begin)
      (setq org-tags-match-list-sublevels 'indented)
    (setq org-tags-match-list-sublevels nil))
  nil)

(defun bh/list-sublevels-for-projects ()
  "Set org-tags-match-list-sublevels so when restricted to a subtree we list all subtasks.
  This is normally used by skipping functions where this variable is already local to the agenda."
  (if (marker-buffer org-agenda-restrict-begin)
      (setq org-tags-match-list-sublevels t)
    (setq org-tags-match-list-sublevels nil))
  nil)

(defvar bh/hide-scheduled-and-waiting-next-tasks t)

(defun bh/toggle-next-task-display ()
  (interactive)
  (setq bh/hide-scheduled-and-waiting-next-tasks (not bh/hide-scheduled-and-waiting-next-tasks))
  (when  (equal major-mode 'org-agenda-mode)
    (org-agenda-redo))
  (message "%s WAITING and SCHEDULED NEXT Tasks" (if bh/hide-scheduled-and-waiting-next-tasks "Hide" "Show")))

(defun bh/skip-stuck-projects ()
  "Skip trees that are not stuck projects"
  (save-restriction
    (widen)
    (let ((next-headline (save-excursion (or (outline-next-heading) (point-max)))))
      (if (bh/is-project-p)
          (let* ((subtree-end (save-excursion (org-end-of-subtree t)))
                 (has-next ))
            (save-excursion
              (forward-line 1)
              (while (and (not has-next) (< (point) subtree-end) (re-search-forward "^\\*+ NEXT " subtree-end t))
                (unless (member "WAITING" (org-get-tags-at))
                  (setq has-next t))))
            (if has-next
                nil
              next-headline)) ; a stuck project, has subtasks but no next task
        nil))))

(defun bh/skip-non-stuck-projects ()
  "Skip trees that are not stuck projects"
  ;; (bh/list-sublevels-for-projects-indented)
  (save-restriction
    (widen)
    (let ((next-headline (save-excursion (or (outline-next-heading) (point-max)))))
      (if (bh/is-project-p)
          (let* ((subtree-end (save-excursion (org-end-of-subtree t)))
                 (has-next ))
            (save-excursion
              (forward-line 1)
              (while (and (not has-next) (< (point) subtree-end) (re-search-forward "^\\*+ NEXT " subtree-end t))
                (unless (member "WAITING" (org-get-tags-at))
                  (setq has-next t))))
            (if has-next
                next-headline
              nil)) ; a stuck project, has subtasks but no next task
        next-headline))))

(defun bh/skip-non-projects ()
  "Skip trees that are not projects"
  ;; (bh/list-sublevels-for-projects-indented)
  (if (save-excursion (bh/skip-non-stuck-projects))
      (save-restriction
        (widen)
        (let ((subtree-end (save-excursion (org-end-of-subtree t))))
          (cond
           ((bh/is-project-p)
            nil)
           ((and (bh/is-project-subtree-p) (not (bh/is-task-p)))
            nil)
           (t
            subtree-end))))
    (save-excursion (org-end-of-subtree t))))

(defun bh/skip-project-trees-and-habits ()
  "Skip trees that are projects"
  (save-restriction
    (widen)
    (let ((subtree-end (save-excursion (org-end-of-subtree t))))
      (cond
       ((bh/is-project-p)
        subtree-end)
       ((org-is-habit-p)
        subtree-end)
       (t
        nil)))))

(defun bh/skip-projects-and-habits-and-single-tasks ()
  "Skip trees that are projects, tasks that are habits, single non-project tasks"
  (save-restriction
    (widen)
    (let ((next-headline (save-excursion (or (outline-next-heading) (point-max)))))
      (cond
       ((org-is-habit-p)
        next-headline)
       ((and bh/hide-scheduled-and-waiting-next-tasks
             (member "WAITING" (org-get-tags-at)))
        next-headline)
       ((bh/is-project-p)
        next-headline)
       ((and (bh/is-task-p) (not (bh/is-project-subtree-p)))
        next-headline)
       (t
        nil)))))

(defun bh/skip-project-tasks-maybe ()
  "Show tasks related to the current restriction.
When restricted to a project, skip project and sub project tasks, habits, NEXT tasks, and loose tasks.
When not restricted, skip project and sub-project tasks, habits, and project related tasks."
  (save-restriction
    (widen)
    (let* ((subtree-end (save-excursion (org-end-of-subtree t)))
           (next-headline (save-excursion (or (outline-next-heading) (point-max))))
           (limit-to-project (marker-buffer org-agenda-restrict-begin)))
      (cond
       ((bh/is-project-p)
        next-headline)
       ((org-is-habit-p)
        subtree-end)
       ((and (not limit-to-project)
             (bh/is-project-subtree-p))
        subtree-end)
       ((and limit-to-project
             (bh/is-project-subtree-p)
             (member (org-get-todo-state) (list "NEXT")))
        subtree-end)
       (t
        nil)))))

(defun bh/skip-project-tasks ()
  "Show non-project tasks.
Skip project and sub-project tasks, habits, and project related tasks."
  (save-restriction
    (widen)
    (let* ((subtree-end (save-excursion (org-end-of-subtree t))))
      (cond
       ((bh/is-project-p)
        subtree-end)
       ((org-is-habit-p)
        subtree-end)
       ((bh/is-project-subtree-p)
        subtree-end)
       (t
        nil)))))

(defun bh/skip-non-project-tasks ()
  "Show project tasks.
Skip project and sub-project tasks, habits, and loose non-project tasks."
  (save-restriction
    (widen)
    (let* ((subtree-end (save-excursion (org-end-of-subtree t)))
           (next-headline (save-excursion (or (outline-next-heading) (point-max)))))
      (cond
       ((bh/is-project-p)
        next-headline)
       ((org-is-habit-p)
        subtree-end)
       ((and (bh/is-project-subtree-p)
             (member (org-get-todo-state) (list "NEXT")))
        subtree-end)
       ((not (bh/is-project-subtree-p))
        subtree-end)
       (t
        nil)))))

(defun bh/skip-projects-and-habits ()
  "Skip trees that are projects and tasks that are habits"
  (save-restriction
    (widen)
    (let ((subtree-end (save-excursion (org-end-of-subtree t))))
      (cond
       ((bh/is-project-p)
        subtree-end)
       ((org-is-habit-p)
        subtree-end)
       (t
        nil)))))

(defun bh/skip-non-subprojects ()
  "Skip trees that are not projects"
  (let ((next-headline (save-excursion (outline-next-heading))))
    (if (bh/is-subproject-p)
        nil
      next-headline)))

Plumbing it into my agenda

(add-to-list 'org-agenda-custom-commands
             '(" " "Agenda"
               ((agenda "" nil)
                (tags "REFILE"
                      ((org-agenda-overriding-header "Tasks to Refile")
                       (org-tags-match-list-sublevels nil)))
                (tags-todo "-NOTDOING/!"
                           ((org-agenda-overriding-header "Stuck Projects")
                            (org-agenda-skip-function 'bh/skip-non-stuck-projects)
                            (org-agenda-sorting-strategy
                             '(category-keep))))
                (tags-todo "-HOLD-NOTDOING/!"
                           ((org-agenda-overriding-header "Projects")
                            (org-agenda-skip-function 'bh/skip-non-projects)
                            (org-tags-match-list-sublevels 'indented)
                            (org-agenda-sorting-strategy
                             '(category-keep))))
                (tags-todo "-NOTDOING/!NEXT"
                           ((org-agenda-overriding-header (concat "Project Next Tasks"
                                                                  (if bh/hide-scheduled-and-waiting-next-tasks
                                                                      ""
                                                                    " (including WAITING and SCHEDULED tasks)")))
                            (org-agenda-skip-function 'bh/skip-projects-and-habits-and-single-tasks)
                            (org-tags-match-list-sublevels t)
                            (org-agenda-todo-ignore-scheduled bh/hide-scheduled-and-waiting-next-tasks)
                            (org-agenda-todo-ignore-deadlines bh/hide-scheduled-and-waiting-next-tasks)
                            (org-agenda-todo-ignore-with-date bh/hide-scheduled-and-waiting-next-tasks)
                            (org-agenda-sorting-strategy
                             '(todo-state-down effort-up category-keep))))
                (tags-todo "-REFILE-NOTDOING-WAITING-HOLD/!"
                           ((org-agenda-overriding-header (concat "Project Subtasks"
                                                                  (if bh/hide-scheduled-and-waiting-next-tasks
                                                                      ""
                                                                    " (including WAITING and SCHEDULED tasks)")))
                            (org-agenda-skip-function 'bh/skip-non-project-tasks)
                            (org-agenda-todo-ignore-scheduled bh/hide-scheduled-and-waiting-next-tasks)
                            (org-agenda-todo-ignore-deadlines bh/hide-scheduled-and-waiting-next-tasks)
                            (org-agenda-todo-ignore-with-date bh/hide-scheduled-and-waiting-next-tasks)
                            (org-agenda-sorting-strategy
                             '(category-keep))))
                (tags-todo "-REFILE-NOTDOING-WAITING-HOLD/!"
                           ((org-agenda-overriding-header (concat "Standalone Tasks"
                                                                  (if bh/hide-scheduled-and-waiting-next-tasks
                                                                      ""
                                                                    " (including WAITING and SCHEDULED tasks)")))
                            (org-agenda-skip-function 'bh/skip-project-tasks)
                            (org-agenda-todo-ignore-scheduled bh/hide-scheduled-and-waiting-next-tasks)
                            (org-agenda-todo-ignore-deadlines bh/hide-scheduled-and-waiting-next-tasks)
                            (org-agenda-todo-ignore-with-date bh/hide-scheduled-and-waiting-next-tasks)
                            (org-agenda-sorting-strategy
                             '(category-keep))))
                (tags-todo "-NOTDOING+WAITING|HOLD/!"
                           ((org-agenda-overriding-header "Waiting and Postponed Tasks")
                            (org-agenda-skip-function 'bh/skip-stuck-projects)
                            (org-tags-match-list-sublevels nil)
                            (org-agenda-todo-ignore-scheduled t)
                            (org-agenda-todo-ignore-deadlines t)))
                (tags "-REFILE/"
                      ((org-agenda-overriding-header "Tasks to Archive")
                       (org-agenda-skip-function 'bh/skip-non-archivable-tasks)
                       (org-tags-match-list-sublevels nil))))
               nil))

Dependencies of the above

(defun bh/find-project-task ()
  "Move point to the parent (project) task if any"
  (save-restriction
    (widen)
    (let ((parent-task (save-excursion (org-back-to-heading 'invisible-ok) (point))))
      (while (org-up-heading-safe)
        (when (member (nth 2 (org-heading-components)) org-todo-keywords-1)
          (setq parent-task (point))))
      (goto-char parent-task)
      parent-task)))

Task types

However we manage projects as a whole, individual tasks can be in the following categories.

(setq org-todo-keywords '((sequence "TODO(t!/!)" "NEXT(x!/!)"  "|" "DONE(d!/!)" "NOTDOING(n@/!)" "LATER(l!/!)" "WAITING(w@/!)")
                          (sequence "EMAIL(e!/!)" "REPLY(r!/!)")))

(setq org-todo-keyword-faces
      (quote (("TODO" :foreground "red" :weight bold)
              ("NEXT" :foreground "blue" :weight bold)
              ("DONE" :foreground "green" :weight bold)
              ("WAITING" :foreground "orange" :weight bold)
              ("NOTDOING" :foreground "green" :weight bold)
              ("LATER" :foreground "orange" :weight bold)
              ("EMAIL" :foreground "red" :weight bold)
              ("REPLY" :foreground "red" :weight bold))))

Keys

(setq org-agenda-window-setup 'current-window)
(global-set-key "\C-cl" 'org-store-link)
(global-set-key "\C-ca" 'org-agenda)
(global-set-key "\C-cc" 'org-capture)
(global-set-key "\C-cb" 'org-iswitchb)
(defun gds/org-current-tasks ()
  "Get an agenda of my current tasks."
  (interactive)
  (org-tags-view t "current"))

(global-set-key (kbd "<f11>") 'gds/org-current-tasks)

Time Management

org-clock-goto

(defun gds/org-clock-in ()
  "Just like org-clock-in, but with default optional argument"
  (interactive)
  (org-clock-in '(4))
  (org-save-all-org-buffers))
;(global-set-key (kbd "<f12>") 'gds/org-clock-in)
(setq org-agenda-restore-windows-after-quit t)
(setq org-clock-persist 'history)
(org-clock-persistence-insinuate)

Clocks

(setq org-clock-history-length 45)

Email bits

(use-package org-mime
  :load-path "~/elisp/org-8.2.10/contrib/lisp/")

Links

The real power of org-mode is being able to link everything to everything else. This is helped a lot by Gnorb.

Link Abbrevs

Sometimes you want to be able to link into something like a bugs database really fast. This is how.

(setq org-link-abbrev-alist
      '(
        ("es5" . "http://www.ecma-international.org/ecma-262/5.1/#sec-%s")
        ("es6" . "http://people.example.com/~jorendorff/es6-draft.html#sec-%s")
        ("tsfile" . "/path/to/org//memacs/files.org_archive::/\*.*%s/")
        ))

Link to an occur

From Email from Carsten Dominik: Re: {O} Is it possible to crea

An "occur" is a search within an emacs buffer (or a set of emacs buffers), quite a lot like grep is for files. I can hyperlink to such a search.

(defun gds/org-occur-open (uri)
  "Visit the file specified by URI, and run `occur' on the fragment
  \(anything after '#') in the uri."
  (let ((list (split-string uri "#")))
    (org-open-file (car list) t)
    (occur (mapconcat 'identity (cdr list) "#"))))
(org-add-link-type "occur" 'gds/org-occur-open)

Fix ediff

Org ediff hack nicked from Email from Michael Brand: Re: {O} {WORG} How to ediff fo

(add-hook 'ediff-prepare-buffer-hook 'gds/ediff-prepare-buffer-hook-setup)
(defun gds/ediff-prepare-buffer-hook-setup ()
  ;; specific modes
  (cond ((eq major-mode 'org-mode)
         (gds/org-vis-mod-maximum))
        ;; room for more modes
        )
  ;; all modes
  (setq truncate-lines nil))
(defun gds/org-vis-mod-maximum ()
  "Visibility: Show the most possible."
  (cond
   ((eq major-mode 'org-mode)
    (visible-mode 1)  ; default 0
    (setq truncate-lines nil)  ; no `org-startup-truncated' in hook
    (setq org-hide-leading-stars t))  ; default nil
   (t
    (message "ERR: not in Org mode")
    (ding))))

Exporting

If you're running something like fci-mode, exported code fragments can get spurious characters inserted into them. Similarly, if your current theme doesn't look good in the web-page or presentation you're preparing. Perhaps there's something to be said for using org-export-before-processing-hook to disable such things before exporting? Unfortunately, I don't see a post-processing hook anywhere…

Reveal and Beamer

In order to export to reveal.js, or to beamer, we'll need the following. This makes use of the ox-latex and ox-beamer libraries which come with org.

(unless (string= (system-name) "example.com")
  (use-package ox-reveal
    :load-path "~/elisp/org-reveal/"
    :defer 5)
  (use-package ox-latex)
  (use-package ox-beamer)
  (add-to-list 'org-latex-classes
               '("beamer"
                 "\\documentclass\[presentation\]\{beamer\}"
                 ("\\section\{%s\}" . "\\section*\{%s\}")
                 ("\\subsection\{%s\}" . "\\subsection*\{%s\}")
                 ("\\subsubsection\{%s\}" . "\\subsubsection*\{%s\}"))))

When writing reveal presentations, we sometimes want to edit in-line LaTeX that org doesn't always recognise.

(defun gds/reveal-togs ()
  "Enable toggling between org-mode and latex-mode."
  (interactive)
  (tog-setup (list 'latex-mode 'org-mode)))

Letters

In order to produce letters we'll need to tell org about the letter class.

(unless (string= (system-name) "example.com")
  (add-to-list 'org-latex-classes
               '("collegeletter"
                 "\\documentclass\{letter\}
\\signature\{My Name\}
\\address\{My Address\\\\
My Address\\\\
My Address\\\\
My Address\\\
My Address\\\\
My Address\}"
                 ("\\section\{%s\}" . "\\section*\{%s\}")
                 ("\\subsection\{%s\}" . "\\subsection*\{%s\}")
                 ("\\subsubsection\{%s\}" . "\\subsubsection*\{%s\}")))

  (add-to-list 'org-latex-classes
               '("personalletter"
                 "\\documentclass\{letter\}
\\signature\{My Name\}
\\address\{My Address\\\\
My Address\\\\
My Address\\\
My Address\\\\
My Address\}"
                 ("\\section\{%s\}" . "\\section*\{%s\}")
                 ("\\subsection\{%s\}" . "\\subsection*\{%s\}")
                 ("\\subsubsection\{%s\}" . "\\subsubsection*\{%s\}")))

  (add-to-list 'org-latex-classes
               '("resume"
                 "\\documentclass[10pt]\{article\}
\\usepackage\{resumewtf\}"
                 ("\\section\{%s\}" . "\\section*\{%s\}")
                 ("\\subsection\{%s\}" . "\\subsection*\{%s\}")
                 ("\\subsubsection\{%s\}" . "\\subsubsection*\{%s\}"))))

Publishing

In order to export large projects, we'll need to set org-publish-project-alist. But we want it set differently for testing and for publishing. So, we have separate functions like gds/publish-offline and gds/publish-online which set that variable up for us.

For this website we also have functions which censor sensitive parts of this configuration during the publishing process.

(defun gds/publish-add-web-personal (web_root)
  "Add my personal web page stuff to `org-publish-project-alist'.

I may want to publish locally, or to the server. WEB_ROOT should
correspondingly be a local path, or a TRAMP one."
  (push (list "personal_web_orgfiles"
              :base-directory "~/Documents/web_personal/"
              :base-extension "org"
              :publishing-directory (concat "" web_root)
              :publishing-function 'org-html-publish-to-html
              :headline-levels 3
              :section-numbers nil
              :with-toc nil
              :html_head "<link rel=\"stylesheet\"
                       href=\"gds.css\" type=\"text/css\"/>"
              :html-preamble t)
            org-publish-project-alist)
  (push (list "personal_web_emacs_configs"
              :base-directory "~/Documents/configs/emacs"
              :base-extension "org"
              :publishing-directory (concat web_root "/configs")
              :publishing-function 'gds/org-publish-to-html-with-privacy
              :headline-levels 3
              :section-numbers t
              :with-toc t
              :html_head "<link rel=\"stylesheet\"
                         href=\"gds.css\" type=\"text/css\"/>"
              :html-preamble t)
        org-publish-project-alist)
  (push (list "personal_web_config_scripts"
              :base-directory "~/Documents/configs/emacs"
              :base-extension "sh"
              :publishing-directory (concat web_root "/configs/")
              :publishing-function 'org-publish-attachment)
        org-publish-project-alist)
  ;; Probably won't work.
  ;; (push (list "personal_web_reveal_example"
  ;;             :base-directory "~/Documents/web_personal/reveal/"
  ;;             :base-extension "org"
  ;;             :publishing-directory (concat "" web_root)
  ;;             :publishing-function 'org-reveal-export-to-html
  ;;             :headline-levels 3
  ;;             :section-numbers nil
  ;;             :with-toc nil
  ;;             :html_head "<link rel=\"stylesheet\"
  ;;                      href=\"gds.css\" type=\"text/css\"/>"
  ;;             :html-preamble t)
  ;;       org-publish-project-alist)
  (push (list "personal_web_images"
              :base-directory "~/Documents/web_personal/imgs/"
              :base-extension "jpg\\|gif\\|png"
              :publishing-directory (concat web_root "/imgs/")
              :publishing-function 'org-publish-attachment)
        org-publish-project-alist)
  (push (list "personal_web_css"
              :base-directory "~/Documents/web_personal/"
              :base-extension "css"
              :publishing-directory (concat "" web_root)
              :publishing-function 'org-publish-attachment)
        org-publish-project-alist)
  (push (list "personal_website" 
              :components (list "personal_web_orgfiles" 
                                "personal_web_images" 
                                "personal_web_css"
                                "personal_web_config_scripts"
                                "personal_web_emacs_configs"))
        org-publish-project-alist))

(defun gds/replace-in-this-buffer (from to)
  "Replace every FROM in the buffer with TO.

Use the style suggested by the help files rather than replace-string."
  (goto-char (point-min))
  (while (search-forward from nil t)
    (replace-match to t t)))

(defun gds/replace-regexp-in-this-buffer (from to)
  "Replace every match of regex FROM in the buffer with TO.

Use the style suggested by the help files rather than replace-regexp."
  (goto-char (point-min))
  (while (re-search-forward from nil t)
    (replace-match to nil nil)))


(defun gds/privacy-filter (text backend info)
  "Remove sensitive config code from export."
  (with-temp-buffer
    (insert text)
    (message "Before setq, case-fold-search is now: %S" case-fold-search)
    (setq case-fold-search nil)
    (message "After setq, case-fold-search is now: %S" case-fold-search)
    (gds/replace-in-this-buffer "example@example.com" "example@example.com")
    (gds/replace-in-this-buffer "example.com" "example.com")
    (gds/replace-in-this-buffer "My Name" "My Name")
    (gds/replace-in-this-buffer "My Address" "My Address")
    (gds/replace-in-this-buffer "My Address" "My Address")
    (gds/replace-in-this-buffer "My Address" "My Address")
    (gds/replace-regexp-in-this-buffer "My Address\\\\\\\" "My Address\\\\")
    (gds/replace-in-this-buffer "My Address" "My Address")
    (gds/replace-in-this-buffer "My Address" "My Address")
    (gds/replace-in-this-buffer "My Address" "My Address")
    (gds/replace-in-this-buffer "My Address" "My Address")
    (gds/replace-in-this-buffer "My Address" "My Address")
    (gds/replace-in-this-buffer "Remote Web Root" "Remote Web Root")
    (gds/replace-in-this-buffer "ssh:example.com" "ssh:example.com")
    (gds/replace-in-this-buffer ":/path/to/mobileorg" ":/path/to/mobileorg")
    (gds/replace-in-this-buffer "/path/to/org/" "/path/to/org/")
    (gds/replace-in-this-buffer "orgfile.org" "orgfile.org")
    (gds/replace-in-this-buffer "orgfile.org" "orgfile.org")
    (gds/replace-in-this-buffer "orgfile.org" "orgfile.org")
    (gds/replace-in-this-buffer "orgfile.org" "orgfile.org")
    (gds/replace-regexp-in-this-buffer "(\"i\" \"IT\" entry (file+olp \"/path/to/org/orgfile.org\" \"HomeIT\")" "")
    (gds/replace-in-this-buffer "FeedURL" "FeedURL")
    (gds/replace-in-this-buffer "FeedName" "FeedName")
    (gds/replace-in-this-buffer "FeedName" "FeedName")
    (gds/replace-in-this-buffer "FeedURL" "FeedURL")
    (gds/replace-in-this-buffer "My Name" "My Name")
    (gds/replace-in-this-buffer "example@example.com" "example@example.com")
    (gds/replace-in-this-buffer "example.com" "example.com")
    (gds/replace-in-this-buffer "example.com" "example.com")
    (gds/replace-in-this-buffer "" "")
    (gds/replace-regexp-in-this-buffer "\\\\*"name"" "\"name\"")
    (gds/replace-in-this-buffer "example.com" "example.com")
    (gds/replace-in-this-buffer "example.com" "example.com")
    (gds/replace-in-this-buffer "ircnet1" "ircnet1")
    (gds/replace-in-this-buffer "ircnet2" "ircnet2")
    (gds/replace-in-this-buffer "ircnet3" "ircnet3")
    (gds/replace-in-this-buffer "ircnet3" "ircnet3")
    (gds/replace-in-this-buffer "6667" "6667")
    (gds/replace-in-this-buffer "example.com" "example.com")
    (gds/replace-in-this-buffer "ircnet4" "ircnet4")
    (gds/replace-in-this-buffer "ircnet5" "ircnet5")
    (gds/replace-in-this-buffer "ircnet5" "ircnet5")
    (gds/replace-in-this-buffer "ircnet5" "ircnet5")
    (gds/replace-in-this-buffer "ircnet6" "ircnet6")
    (gds/replace-in-this-buffer "ircnet6" "ircnet6")
    (gds/replace-in-this-buffer "ircnet7" "ircnet7")
    (gds/replace-in-this-buffer "ircnet7" "ircnet7")
    (gds/replace-in-this-buffer "ircnet7" "ircnet7")
    (gds/replace-in-this-buffer "example.com" "example.com")
    (gds/replace-in-this-buffer "example.com" "example.com")
    (gds/replace-in-this-buffer "example.com" "example.com")
    (gds/replace-in-this-buffer "example.com" "example.com")
    (gds/replace-in-this-buffer "example.com" "example.com")
    (gds/replace-in-this-buffer "example.com" "example.com")
    (gds/replace-in-this-buffer "example.com" "example.com")
    (gds/replace-in-this-buffer "example.com" "example.com")
    (gds/replace-in-this-buffer "#channel" "#channel")
    (gds/replace-in-this-buffer "#channel" "#channel")
    (gds/replace-in-this-buffer "#channel" "#channel")
    (gds/replace-in-this-buffer "#channel" "#channel")
    (gds/replace-in-this-buffer "#channel" "#channel")
    (gds/replace-in-this-buffer "#channel" "#channel")
    (gds/replace-in-this-buffer "#channel" "#channel")
    (gds/replace-in-this-buffer "#channel" "#channel")
    (gds/replace-in-this-buffer "#channel" "#channel")
    (gds/replace-in-this-buffer "#channel" "#channel")
    (gds/replace-in-this-buffer "#channel" "#channel")
    (gds/replace-in-this-buffer "/path/to/org-config.org" "/path/to/org-config.org")
    (gds/replace-in-this-buffer "example@example.com" "example@example.com")
    (gds/replace-in-this-buffer "example@example.com" "example@example.com")
    (gds/replace-in-this-buffer "example@example.com" "example@example.com")
    (gds/replace-in-this-buffer "example@example.com" "example@example.com")
    (gds/replace-in-this-buffer "example@example.com" "example@example.com")
    (gds/replace-in-this-buffer "example@example.com" "example@example.com")
    (gds/replace-in-this-buffer "example@example.com" "example@example.com")
    (gds/replace-in-this-buffer "example@example.com" "example@example.com")
    (gds/replace-in-this-buffer "example@example.com" "example@example.com")
    (gds/replace-in-this-buffer "example@example.com" "example@example.com")
    (gds/replace-in-this-buffer "example@example.com" "example@example.com")
    (gds/replace-in-this-buffer "example@example.com" "example@example.com")
    (gds/replace-in-this-buffer "example@example.com" "example@example.com")
    (gds/replace-in-this-buffer "example@example.com" "example@example.com")
    (gds/replace-in-this-buffer "example@example.com" "example@example.com")
    (gds/replace-in-this-buffer "example@example.com" "example@example.com")
    (gds/replace-in-this-buffer "example@example.com" "example@example.com")
    (gds/replace-in-this-buffer "example@example.com" "example@example.com")
    (gds/replace-in-this-buffer "example@example.com" "example@example.com")
    (gds/replace-in-this-buffer "example@example.com" "example@example.com")
    (gds/replace-in-this-buffer "path" "path")
    (gds/replace-in-this-buffer "path" "path")
    (gds/replace-in-this-buffer "path" "path")
    (gds/replace-in-this-buffer "abbrev" "abbrev")
    (gds/replace-in-this-buffer "path" "path")
    (gds/replace-in-this-buffer "abbrev" "abbrev")
    (gds/replace-in-this-buffer "path" "path")
    (gds/replace-in-this-buffer "abbrev" "abbrev")
    (gds/replace-in-this-buffer "path" "path")
    (gds/replace-in-this-buffer "path" "path")
    (gds/replace-in-this-buffer "abbrev" "abbrev")
    (gds/replace-in-this-buffer "abbrev" "abbrev")
    (gds/replace-in-this-buffer "abbrev" "abbrev")
    (gds/replace-in-this-buffer "path" "path")
    (gds/replace-in-this-buffer "abbrev" "abbrev")
    (gds/replace-in-this-buffer "path" "path")
    (gds/replace-in-this-buffer "abbrev" "abbrev")
    (gds/replace-in-this-buffer "path" "path")
    (gds/replace-in-this-buffer "abbrev" "abbrev")
    (gds/replace-in-this-buffer "/path/to/my/emacslive/hack" "/path/to/my/emacslive/hack")
    (gds/replace-in-this-buffer "example.com" "example.com")
    (gds/replace-in-this-buffer "example.com" "example.com")
    (gds/replace-in-this-buffer "example.com" "example.com")
    (gds/replace-in-this-buffer "example.com" "example.com")
    (gds/replace-in-this-buffer "example.com" "example.com")
    (gds/replace-in-this-buffer "example.com" "example.com")
    (gds/replace-in-this-buffer "example.com.doc" "example.com")
    (gds/replace-in-this-buffer "example.com" "example.com")
    (gds/replace-in-this-buffer "Author: Gareth Smith" "Author: Gareth Smith")
    (buffer-string)))

(defun gds/org-publish-to-html-with-privacy (plist filename pub-dir)
  "Wrapper for org-html-publish-to-html with a privacy filter.

Set up the privacy filter before calling
org-html-publish-to-html, then reset to the old filter list."
  (let ((old-final-output-filter-functions (copy-list org-export-filter-final-output-functions)))
    (add-to-list 'org-export-filter-final-output-functions
                 'gds/privacy-filter)
    (org-html-publish-to-html plist filename pub-dir)
    (setq org-export-filter-final-output-functions nil) ; Hack while I get the next line right :P
    (setq org-export-filter-final-output-functions old-final-output-filter-functions)))




(defun gds/publish-online ()
  "Set my publishing sources to be live - to push to servers."
  (interactive)
  (setq org-publish-project-alist nil)
  (gds/publish-add-web-personal "Remote Web Root"))

(defun gds/publish-offline ()
  "Set my publishing sources to be offline - to push to temporary local directories."
  (interactive)
  (setq org-publish-project-alist nil)
  (gds/publish-add-web-personal "~/Documents/web_personal/tmp"))

(gds/publish-offline)

TODO fix gds/org-publish-to-html-with-privacy

It should put org-export-filter-final-output-functions back how it found it, not just nuke it.

Sync

Since I keep my life in my org files, I like to be able to access them on my phone. For this I use MobileOrg.

(setq org-mobile-directory "/ssh:example.com:/path/to/mobileorg/")
(setq org-directory "/path/to/org/")
(setq org-mobile-inbox-for-pull "/path/to/org/orgfile.org")

Dead Sync Code

I used to try to use org mobile sync like an autosave function, but doing that over tramp for a whole bunch of files is too expensive. Now I use Online offline functions to sync at the beginning and end of each work session. That seems to work better for me.

;; (defvar org-mobile-push-timer nil  "Timer that `org-mobile-push-timer' used to reschedule itself, or nil.")
;; (defun org-mobile-push-with-delay (secs) \n  (when org-mobile-push-timer    (cancel-timer org-mobile-push-timer))  (setq org-mobile-push-timer        (run-with-idle-timer         (* 1 secs) nil 'org-mobile-push)))
;; (add-hook 'after-save-hook  (lambda ()    (when (eq major-mode 'org-mode)     (dolist (file (org-mobile-files-alist))       (if (string= (expand-file-name (car file)) (buffer-file-name))           (org-mobile-push-with-delay 30)))   )))
;; (run-at-time "00:05" 86400 '(lambda () (org-mobile-push-with-delay 1)))
;; refreshes agenda file each day

                                        ;(org-mobile-pull)
;; run org-mobile-pull at startup
;; (defun install-monitor (file secs)  (run-with-timer   0 secs   (lambda (f p)     (unless (< p (second (time-since (elt (file-attributes f) 5))))       (org-mobile-pull)))   file secs))
;; (install-monitor (file-truename                  (concat                   (file-name-as-directory org-mobile-directory)                          org-mobile-capture-file))                 5)
;; Do a pull every 5 minutes to circumvent problems with timestamping
;; (ie. dropbox bugs)
;; (run-with-timer 0 (* 5 60) 'org-mobile-pull)

File extensions

(add-to-list 'auto-mode-alist '("\\.notes$" . org-mode))
(add-to-list 'auto-mode-alist '("\\.org_archive$" . org-mode))

Capture

As mentioned above, I use org-expiry to give some of my tasks a best-before date.

To do this, we need to functions to get the date tomorrow, in a week, two weeks, etc.

(defun gds/today ()
  "Get today's date as a string."
  (format-time-string "%F" (current-time)))

(defun gds/tomorrow ()
  "Get tomorrow's date as a string."
  (format-time-string "%F" (time-add (current-time) (days-to-time 1))))

(defun gds/next-week ()
  "Get next week's date as a string."
  (format-time-string "%F" (time-add (current-time) (days-to-time 7))))

(defun gds/in-a-fortnight ()
  "Get next fortnight's date as a string."
  (format-time-string "%F" (time-add (current-time) (days-to-time 14))))

(defun gds/next-month ()
  "Get next month's date as a string."
  (cl-destructuring-bind (sec min hour day month year dow dst zone)
      (decode-time (current-time))
    (format-time-string "%F" (encode-time 0 0 0 day (+ 1 month) year))))

(defun gds/in-two-months ()
  "Get two month's date as a string."
  (cl-destructuring-bind (sec min hour day month year dow dst zone)
      (decode-time (current-time))
    (format-time-string "%F" (encode-time 0 0 0 day (+ 2 month) year))))

The actual capture configuration is now pretty standard:

(setq org-capture-templates
      '(("n" "iNterruption" entry (file+datetree "/path/to/org/orgfile.org")
         "* %?\n  %a\n  %K" ;; :clock-in t :clock-resume t
         )
        ("k" "breaK" entry (file+datetree "/path/to/org/orgfile.org")
         "* %?\n  %a\n  %K" ;; :clock-in t :clock-resume t
         )
        ("t" "Todo" entry (file "/path/to/org/orgfile.org")
         "* TODO %? \n  %a\n  %K" ;; :clock-in t :clock-resume t
         )
        ("1" "Todo by tomorrow" entry (file "/path/to/org/orgfile.org")
         "* TODO %? \n  :PROPERTIES:\n  :EXPIRY:   [%(gds/tomorrow)]\n  :END:\n  %a\n  %K"
         )
        ("2" "Todo within a week" entry (file "/path/to/org/orgfile.org")
         "* TODO %? \n  :PROPERTIES:\n  :EXPIRY:   [%(gds/next-week)]\n  :END:\n  %a\n  %K"
         )
        ("3" "Todo within a fortnight" entry (file "/path/to/org/orgfile.org")
         "* TODO %? \n  :PROPERTIES:\n  :EXPIRY:   [%(gds/in-a-fortnight)]\n  :END:\n  %a\n  %K"
         )
        ("4" "Todo within a month" entry (file "/path/to/org/orgfile.org")
         "* TODO %? \n  :PROPERTIES:\n  :EXPIRY:   [%(gds/next-month)]\n  :END:\n  %a\n  %K"
         )
        ("5" "Todo within two months" entry (file "/path/to/org/orgfile.org")
         "* TODO %? \n  :PROPERTIES:\n  :EXPIRY:   [%(gds/in-two-months)]\n  :END:\n  %a\n  %K"
         )
        ("i" "IT" entry (file+olp "/path/to/org/orgfile.org" "HomeIT")
         "* TODO %? \n  %a\n  %K" ;; :clock-in t :clock-resume t
         )
        ("s" "Spam" item (file+olp "/path/to/org/orgfile.org" "HomeIT" "Spam")
        "%a\n  %K" :immediate-finish t)
        ("l" "Link" entry (file+olp "/path/to/org/orgfile.org" "Random" "Links")
         "* %a %? %K\n  :PROPERTIES:\n  :CREATED:   %U\n  :END:" :unnarrowed t ;; :clock-in t :clock-resume t
         )
        ("m" "Music" entry (file+olp "/path/to/org/orgfile.org" "Random" "Music")
         "* %? %a %K\n  :PROPERTIES:\n  :CREATED:   %U\n  :END:" :unnarrowed t ;; :clock-in t :clock-resume t
         )
        ("b" "Blog" entry (file+olp "/path/to/org/website.org" "Blog ideas")
         "* TODO %? \n  %a\n  %K" ;; :clock-in t :clock-resume t
         )
        ("c" "Current" entry (clock)
         "* %?\n  %a\n  %K" ;; :clock-in t :clock-resume t
         )))

(setq org-clock-into-drawer t)
(setq org-log-into-drawer t)
(setq org-clock-idle-time 10)

We can also use the Firefox org-capture plugin to capture from web pages – so long as we enable the protocol like so:

(use-package org-protocol)

Refilling

Since I capture a lot of tasks into a single large inbox, it helps to have some functions that make it easy to re-file those tasks elsewhere.

A lot of this is shamelessly stolen from Bernt Hansen.

Targets include this file and any file contributing to the agenda - up to 9 levels deep

(setq org-refile-targets (quote ((nil :maxlevel . 9)
                                 (org-agenda-files :maxlevel . 9))))

Use full outline paths for refile targets - we file directly with IDO Helm

(setq org-refile-use-outline-path t)

Targets complete directly with IDO Helm

(setq org-outline-path-complete-in-steps nil)

Allow refile to create parent tasks with confirmation

(setq org-refile-allow-creating-parent-nodes (quote confirm))

Exclude DONE state tasks from refile targets

(defun bh/verify-refile-target ()
  "Exclude todo keywords with a done state from refile targets"
  (not (member (nth 2 (org-heading-components)) (quote "DONE")))) ;Note - "org-done-keywords"?

(setq org-refile-target-verify-function 'bh/verify-refile-target)

Babel

Babel makes it possible to embed source code of any language into your org-files. You can then pretty-print that code, or run it as you wish. This makes org a great platform for Literate Programming as championed by Don Knuth. These config files are written this way, and these web pages are generated automatically by org.

What languages?

(org-babel-do-load-languages
 'org-babel-load-languages
 '((js . t)
   (haskell . t)
   (ledger . t)
   (emacs-lisp . t)
   (sh . t)))

Proper syntax highlighting

(setq org-src-fontify-natively t)

Memacs and other history

When I use v A in the agenda buffer, this causes an large number of large org archive files to open. Sometimes I might want to close them all to free up some space.

(defun gds/get-org-archive-buffers ()
  "Get a list of org archive buffers"
  (-filter (lambda (buf) (search ".org_archive" (buffer-name buf))) (buffer-list)))

(defun gds/kill-org-archive-buffers ()
  "Kill archive buffers to save space."
  (interactive)
  (save-some-buffers)
  (cl-loop for buf in (gds/get-org-archive-buffers) do
           (kill-buffer buf)))

LaTeX

(remove-hook 'org-mode-hook 'turn-on-org-cdlatex)

TODO Add a nixos section with tramp magic

:header-args: :tangle no

Set the tramp-methods variable to allow me to use tramp to access nixos "environments".

My current best go at this is here, but currently not very successful. The nixosEnv method doesn't even open. the ff method seems to work until you try to use it for something, at which point it can't find programs installed in the firefoxEnv environment.

…this seems to be because TRAMP uses getconf to get the value of the PATH variable. It then sets the $PATH to whatever getconf said it was (plus anything in tramp-remote-path ?) Unfortunately, in nixos, getconf reports the current system path, but what we're actually after is the current $PATH – which has been modified by the load-env.

Ideally I'd like to surpress tramp's attempt to change the PATH. Failing that, I guess I could try to knobble getconf…

…aha, perhaps I should be using "Private Directories" in the tramp-remote-path var?

(if (string-prefix-p "fire" (system-name))
    (progn
      (add-to-list 'tramp-methods
                    ;; A bunch of examples taken from the existing default value
                    ;; ("sudo"
                    ;;   (tramp-login-program "sudo")
                    ;;   (tramp-login-args
                    ;;    (("-u" "%u")
                    ;;     ("-s")
                    ;;     ("-H")
                    ;;     ("-p" "Password:")))
                    ;;   (tramp-login-env
                    ;;    (("SHELL")
                    ;;     ("/bin/sh")))
                    ;;   (tramp-remote-shell "/bin/sh")
                    ;;   (tramp-remote-shell-args
                    ;;    ("-c"))
                    ;;   (tramp-connection-timeout 10))
                    ;; ("su"
                    ;;  (tramp-login-program "su")
                    ;;  (tramp-login-args
                    ;;   (("-")
                    ;;    ("%u")))
                    ;;  (tramp-remote-shell "/bin/sh")
                    ;;  (tramp-remote-shell-args
                    ;;   ("-c"))
                    ;;  (tramp-connection-timeout 10))
                    ;; ("telnet"
                    ;;  (tramp-login-program "telnet")
                    ;;  (tramp-login-args
                    ;;   (("%h")
                    ;;    ("%p")))
                    ;;  (tramp-remote-shell "/bin/sh")
                    ;;  (tramp-remote-shell-args
                    ;;   ("-c"))
                    ;;  (tramp-default-port 23))
                    '("nixosEnv"
                     (tramp-login-program "/home/gds/bin/gds-nix-load-env")
                     (tramp-remote-shell "/bin/sh")
                     (tramp-login-args
                      ("-u" "%u"))
                     (tramp-login-env
                      (("SHELL")
                       ("/bin/sh")))
                     (tramp-remote-shell "/bin/sh")
                     (tramp-remote-shell-args
                      ("-c"))
                     (tramp-connection-timeout 10)))
      (add-to-list 'tramp-methods
                   '("ff"
                     (tramp-login-program "load-env-firefoxEnv")
                     (tramp-remote-shell "/bin/sh")
                     (tramp-login-env
                      (("SHELL")
                       ("/bin/sh"))
                      (("PATH")
                       ("$PATH")))
                     (tramp-remote-shell-args
                      ("-c"))
                     (tramp-connection-timeout 10)))
))

TODO Feeds

Let's experiment with some RSS feeds in org. Can they be as useful as gnus? One major advantage might be offline reading with mobileorg. Documentation for this feature is here.

(setq org-feed-alist
      '(("FeedName"
         "FeedURL"
         "/path/to/org/feeds.org" "FeedName")
        ("FeedName"
         "FeedURL"
         "/path/to/org/feeds.org" "FeedName")))

`C-c C-x g (`org-feed-update-all')'

`C-c C-x g' Collect items from the feeds configured in `org-feed-alist' and act upon them.

`C-c C-x G (`org-feed-goto-inbox')' Prompt for a feed name and go to the inbox configured for this feed.

Author: Gareth Smith

Created: 2015-08-29 Sat 22:18

Validate