(add-to-list 'load-path "~/notebook/emacs/dev/muse/lisp") ;(add-to-list 'load-path "/home/sacha/notebook/emacs") ;(add-to-list 'load-path "/home/sacha/notebook/emacs/planner-muse") (setq muse-mode-auto-p t) (require 'muse-mode) (require 'muse-project) (require 'muse-html) (require 'muse-wiki) ;(require 'planner) (require 'muse-colors) ;;;_+ When I say derive a style, I mean derive a style (require 'assoc) (defun muse-define-style (name &rest elements) (aput 'muse-publishing-styles name elements)) (defun muse-derive-style (new-name base-name &rest elements) (aput 'muse-publishing-styles new-name (append elements (list :base base-name)))) ;;;_+ Some setup (setq muse-publishing-styles (delq (assoc "my-html" muse-publishing-styles) muse-publishing-styles)) (setq muse-publishing-styles (delq (assoc "my-rss" muse-publishing-styles) muse-publishing-styles)) ;; Let me use =) smilies again (setq muse-publish-markup-regexps (delq '(1600 "\\(^\\|[-[[:space:]<('`\"]\\)\\(=[^=[:space:]]\\|_[^_[:space:]]\\|\\*+[^*[:space:]]\\)" 2 word) muse-publish-markup-regexps)) (defadvice muse-publish-markup-word (around sacha activate) "Do nothing.") (muse-derive-style "my-rss" "planner-html" :header "" :footer "") (setq muse-project-alist `(("WikiPlanner" (,@(muse-project-alist-dirs "~/notebook/plans") :default "index" :major-mode planner-mode :visit-link planner-visit-link) (:base "planner-html" :header "~/notebook/wiki/.header.muse" :footer "" :before sacha/planner-before-markup :suffix ".php" :osuffix ".php" :path "~/notebook/wiki")))) (setq muse-html-style-sheet nil) (setq muse-html-header "~/notebook/wiki/.header.muse") (setq muse-html-footer "") (defun sacha/muse-project-publish-file () "Publish the current file." (interactive) (when (and (buffer-file-name) muse-current-project) (let ((styles (cddr muse-current-project)) (project muse-current-project) published) (unless styles (setq styles (list (muse-publish-get-style)))) (run-hook-with-args 'muse-before-project-publish-hook project) (setq published (muse-project-publish-file (buffer-file-name) styles t)) (run-hook-with-args 'muse-after-project-publish-hook project)))) (add-to-list 'load-path "/usr/src/planner-el") (require 'planner) (require 'planner-timeclock) (require 'planner-multi) (require 'planner-lisp) (require 'planner-cyclic) (require 'planner-gnus) (require 'planner-bbdb) (setq planner-project "WikiPlanner") (setq muse-file-extension "txt") (require 'planner-publish) (require 'planner-rss) ;;_+ Keybindings ;; This reminds me what I'm working on. C-u F9 F9 jumps to the task, too. (global-set-key (kbd " p SPC") 'planner-goto-today) (global-set-key (kbd " P SPC") 'planner-goto) (global-set-key (kbd " r SPC") 'remember) (global-set-key (kbd " R SPC") 'remember-region) (global-set-key (kbd " t SPC") 'planner-create-task-from-buffer) (global-set-key (kbd " T SPC") 'planner-create-task) ;; I use F9 p to go to today's page, anyway. (define-key planner-mode-map (kbd "C-c C-n") 'planner-create-note-from-task) (define-key planner-mode-map (kbd "C-c C-e") 'planner-edit-task-description) ;; I use an after-save-hook to publish, so I can remap C-c C-p (define-key planner-mode-map (kbd "C-c C-p") 'planner-task-pending) (define-key planner-mode-map (kbd "C-c C-i") 'planner-task-in-progress) ;;;_+ Basic setup (setq planner-directory "/home/sacha/notebook/plans") (setq planner-publishing-directory "/home/sacha/public_html/notebook/wiki") (setq planner-carry-tasks-forward t) (setq planner-expand-name-favor-future-p nil) (setq planner-task-dates-favor-future-p t) (setq planner-default-task-priority "B") (setq planner-expand-name-default ".") (setq planner-task-format "#%s%s %s %s%s") ;; I don't need my tasks renumbered. (setq planner-renumber-tasks-automatically nil) (setq planner-align-tasks-automatically nil) (setq planner-renumber-notes-automatically nil) ;; Do not automatically add task IDs. I used to set this to non-nil, ;; but realized that I didn't edit my task descriptions that often. If ;; I want to edit a task, I can just add the task ID _before_ editing. (setq planner-id-add-task-id-flag nil) ;; I don't mind having lots of open planner files (setq planner-tasks-file-behavior nil) ;;;_+ planner-rss configuration (setq planner-rss-base-url "http://sachachua.com/notebook/wiki/") (setq planner-rss-category-feeds '(("ShortStories\\|flash" "/home/sacha/notebook/wiki/flash.rdf" "") ("planner" "/home/sacha/notebook/wiki/planner.rdf" "") ("WeeklyReport" "/home/sacha/notebook/wiki/weekly-burn.rdf" "") ("education\\|teaching\\|students\\|graduate" "/home/sacha/notebook/wiki/education.rdf" "") ("cook" "/home/sacha/notebook/wiki/cook.rdf" "") (" sachachua's blog http://sachachua.com/notebook/wiki/today.php Random notes "))) (setq planner-rss-feed-limits '(("." 100000 nil))) ;;;_+ Chronological notes on day pages and reverse-chronological on plan pages (defun sacha/planner-twiddle-chronological-notes () "Use chronological notes on day pages and reverse-chronological notes on plan pages. People visit my site once a day, so chronologically-ordered notes are easier for them to understand. People visit plan pages less often, so new things should be closer to the top." (set (make-variable-buffer-local 'planner-reverse-chronological-notes) (not (string-match planner-date-regexp (or (planner-page-name) ""))))) (add-hook 'planner-mode-hook 'sacha/planner-twiddle-chronological-notes) ;;;_+ Compatibility for old pages or old code ;;; Compatibility, purely for old pages I'm too lazy to change. ;;; planner-diary is so much cooler. (defun sacha/planner-get-diary-entries (date) "For DATE (yyyy.mm.dd), return a list of diary entries as a string." (require 'diary-lib) (when (string-match planner-date-regexp date) (let* ((diary-display-hook 'ignore) (entries (list-diary-entries (list (string-to-number (match-string 2 date)) ; month (string-to-number (match-string 3 date)) ; day (string-to-number (match-string 1 date))) ; year 1))) ; Get entries for one day (if entries (mapconcat (lambda (item) (nth 1 item)) entries "\n") nil)))) (fset 'planner-get-diary-entries 'sacha/planner-get-diary-entries) ;;;_+ planner-diary ;;; Here we use planner-diary. ;(setq planner-diary-string "* ~/.diary schedule") ;(setq planner-diary-use-diary t) ;(planner-diary-insinuate) ;; Just in case? ;;(defadvice plan (after sacha) ;; "Call `planner-diary-insert-diary'." ;; (planner-diary-insert-diary)) ;;; (defun sacha/planner-diary-schedule-task (start end) ;;; "Add a diary entry for the current task from START to END." ;;; (interactive "MStart: \nMEnd: ") ;;; (save-window-excursion ;;; (save-excursion ;;; (save-restriction ;;; (let* ((info (planner-current-task-info)) ;;; (original (planner-task-description info)) ;;; main ;;; description) ;;; ;; TODO: Mark the task as scheduled for a particular time ;;; (setq description ;;; (cond ;;; ((string-match "^\\(.+\\)\\s-+{{Schedule:\\([^-]+\\)-\\([^}]+\\)}}\\(.*\\)" original) ;;; (setq main (match-string 1 original)) ;;; (save-excursion ;;; (save-match-data ;;; (goto-char (point-min)) ;;; (when (re-search-forward ;;; (concat (match-string 2 original) ;;; " | " ;;; (match-string 3 original) ;;; " | " ;;; (match-string 1 original)) ;;; nil t) ;;; (sacha/planner-diary-unschedule-entry)))) ;;; (concat (match-string 1 original) ;;; " {{Schedule:" ;;; start ;;; "-" ;;; end ;;; "}}" ;;; (match-string 4 original))) ;;; ((string-match "\\(.*\\)\\(\\s-*\\)$" original) ;;; (setq main (match-string 1 original)) ;;; (replace-match (concat " {{Schedule:" start "-" end "}}") ;;; t t original 2)))) ;;; (planner-edit-task-description description) ;;; ;; Add the diary entry ;;; (sacha/planner-diary-add-entry ;;; (planner-task-date info) ;;; (concat start " | " end " | " main))))))) ;;; (defun sacha/planner-diary-add-entry (date text &optional annotation) ;;; "Prompt for a diary entry to add to `diary-file'." ;;; (interactive ;;; (list ;;; (if (or current-prefix-arg ;;; (not (string-match planner-date-regexp (planner-page-name)))) ;;; (planner-read-date) ;;; (planner-page-name)) ;;; (read-string ;;; "Diary entry: "))) ;;; (save-excursion ;;; (save-window-excursion ;;; (let ((inhibit-read-only t)) ;;; (make-diary-entry ;;; (concat ;;; (let ((cal-date (planner-filename-to-calendar-date date))) ;;; (calendar-date-string cal-date t t)) ;;; " " text ;;; (or annotation ;;; (let ((annotation (run-hook-with-args-until-success ;;; 'planner-annotation-functions))) ;;; (if annotation ;;; (concat " " annotation) ;;; "")))))) ;;; (planner-goto date) ;;; (planner-diary-insert-diary-maybe)))) ;;; (defun sacha/planner-diary-unschedule-entry () ;;; "Unschedule the current entry." ;;; (interactive) ;;; (goto-char (line-beginning-position)) ;;; (let ((id ;;; (if (re-search-forward "{{Tasks:\\([^}]+\\)}}" (line-end-position) t) ;;; (match-string 0) ;;; nil))) ;;; (sacha/planner-diary-delete-entry) ;;; (when id ;;; (planner-seek-to-first "Tasks") ;;; (re-search-forward id nil t)))) ;;; (defun sacha/planner-diary-delete-entry () ;;; "Delete the current entry from `diary-file'." ;;; (interactive) ;;; (let ((cal-date (planner-filename-to-calendar-date (planner-page-name))) ;;; (text (buffer-substring (line-beginning-position) ;;; (line-end-position))) ;;; (case-fold-search nil)) ;;; (save-excursion ;;; (save-window-excursion ;;; (let ((inhibit-read-only t)) ;;; (find-file diary-file) ;;; (save-excursion ;;; (save-restriction ;;; (widen) ;;; (goto-char (point-max)) ;;; (when (re-search-backward ;;; (concat "^" ;;; (regexp-quote ;;; (concat (calendar-date-string cal-date t t) ;;; " " text)))) ;;; (delete-region (line-beginning-position) ;;; (min (1+ (line-end-position)) (point-max)))) ;;; (save-buffer)))) ;;; (planner-diary-insert-diary-maybe t))))) ;;; (define-key planner-mode-map (kbd "C-c C-s") 'sacha/planner-diary-schedule-task) ;;; (define-key planner-mode-map (sacha/gnu-vs-x (kbd "C-c C-S-s") ;;; (kbd "C-c C-S")) ;;; 'sacha/planner-diary-unschedule-entry) ;;;_+ RSS blogging (eval-after-load 'remember-planner '(add-to-list 'remember-planner-append-hook 'planner-rss-add-note t)) (defun sacha/planner-rss-published-file (page) ;; Totally ugly hack (concat "http://sachachua.com/notebook/wiki/" page ".php")) (defvar sacha/muse-use-absolute-url-flag nil "Non-nil means use absolute URL flag.") (defun sacha/muse-expand-absolute-url (url &rest ignored) "Expand URL to an absolute one." (if sacha/muse-use-absolute-url-flag (w3m-expand-url url "http://sachachua.com/notebook/wiki/") url)) (add-to-list 'muse-publish-url-transforms 'sacha/muse-expand-absolute-url) ;;(adelete 'muse-publishing-styles "planner-rss") (muse-derive-style "sacha-rss" "planner-rss" :published-file-function 'sacha/planner-rss-published-file) (defadvice planner-rss-add-note (around sacha/absolute-urls activate) "Publish absolute URLs." (let ((sacha/muse-use-absolute-url-flag t) (muse-publishing-current-style (muse-style "sacha-rss")) (muse-xml-markup-specials nil)) ;'((?\" . """) ; (?\< . "<") ; (?\> . ">") ; (?\& . "&")))) (setq ad-return-value ad-do-it))) (defadvice planner-rss-add-note (around sacha/norss activate) "Do not publish if note includes \"norss\"" (save-restriction (when (planner-narrow-to-note) (goto-char (point-min)) (unless (search-forward "norss" nil t) ad-do-it)))) (defun sacha/rss-delete-item () (interactive) (let ((inhibit-read-only t)) (delete-region (if (looking-at "") (point) (when (re-search-backward "" nil t) (match-beginning 0))) (when (re-search-forward "" nil t) (match-end 0))))) (defun sacha/planner-update-note () "Update this note in RSS and Planner." (interactive) (let ((inhibit-read-only t)) (sacha/planner-rss-undo-this-note) (planner-update-note) (planner-rss-add-note))) (defun sacha/planner-rss-undo-this-note () "Delete the current entry from the RDFs it matched." (interactive) (save-excursion (save-restriction (planner-narrow-to-note) (let* ((feeds planner-rss-category-feeds) (info (planner-current-note-info)) (page (concat "" (sacha/planner-rss-published-file (muse-page-name)) "#anchor-" (planner-note-anchor info) "")) files) (while feeds (goto-char (point-min)) (let ((criterion (car (car feeds))) (file (car (cdr (car feeds))))) (if (if (functionp criterion) (funcall criterion) (re-search-forward criterion nil t)) (add-to-list 'files file)) (setq feeds (cdr feeds)))) (while files (with-current-buffer (find-file-noselect (car files)) (goto-char (point-min)) (when (re-search-forward page nil t) (sacha/rss-delete-item) (save-buffer))) (setq files (cdr files))))))) ;;;_+ Misc (defun sacha/planner-replan-region (beg end &optional page) "Replan all tasks from BEG to END to PAGE." (interactive (list (point) (mark) (planner-read-name (planner-file-alist) "Replan to: "))) (let ((start (if (< beg end) beg end)) (finish (if (< beg end) end beg))) ;; Invoke planner-copy-or-move-task on each line in reverse (save-excursion (save-restriction (narrow-to-region (and (goto-char start) (line-beginning-position)) (and (goto-char (1- finish)) (min (point-max) (1+ (line-end-position))))) (goto-char (point-min)) (while (not (eobp)) (save-excursion (save-restriction (planner-replan-task page))) (forward-line 1)))))) ;;;_+ 20040504: Relative annotations (setq planner-annotation-use-relative-file (lambda (filename) "Use relative filename if FILENAME is under my home directory." (save-match-data (or (string-match "^/home/sacha" filename) (string-match "^/mnt/data/home/sacha" filename) (string-match "^/mnt/media/sacha" filename))))) ;;;_+ Publishing strings (defvar sacha/muse-html-suppress-paragraphs nil "Non-nil means do not mark up paragraphs.") (defun sacha/muse-publish-markup-string (string &optional style) "Markup STRING using the given STYLE's markup rules." (setq style (muse-style style)) (muse-with-temp-buffer (insert string) (let ((muse-publishing-current-style style) (muse-publishing-p t) (sacha/muse-html-suppress-paragraphs t)) (muse-publish-markup-region (point-min) (point-max) "*string*" (muse-style style))) (buffer-string))) (defadvice muse-html-markup-paragraph (around sacha activate) "Suppress if sacha/muse-html-suppress-paragraphs is non-nil." (unless sacha/muse-html-suppress-paragraphs ad-do-it)) ;;;_+ Permalinks and comments (defun sacha/planner-note-id (info) "Return the note identifier for commenting systems. Prefers date pages." (planner-replace-regexp-in-string "[#\\.]" "-" (if (string-match planner-date-regexp (planner-note-page info)) (concat (planner-note-page info) "#" (planner-note-anchor info)) (let (found (pages (planner-multi-split (planner-note-link-text info)))) (while pages (when (string-match planner-date-regexp (planner-link-base (car pages))) (setq found (planner-link-target (car pages)) pages nil)) (setq pages (cdr pages))) (or found (concat (planner-note-page info) "#" (planner-note-anchor info))))))) (defun sacha/planner-skip-private () "Remove all lines matching {{private}}." (goto-char (point-min)) (while (re-search-forward "{{private}}.*" nil t) (replace-match ""))) ;; I want notes preceded by a number so I know how to link to them. (defun sacha/planner-markup-notes () "Mark up notes neatly." (let ((muse-publish-markup-tags muse-publish-markup-tags)) (add-to-list 'muse-publish-markup-tags '("tag" nil t nil sacha/planner-technorati-tag)) (while (re-search-forward "^\\.#[0-9]" nil t) (save-restriction (planner-narrow-to-note) (let* ((info (planner-current-note-info t)) (id (sacha/planner-note-id info)) (sacha/planner-technorati-tag-do-markup t) (permalink (concat "http://sachachua.com/notebook/wiki/" (planner-page-name) ".php#anchor-" (planner-note-anchor info)))) (delete-region (point-min) (point-max)) ;; Bound the entire thing in a div (planner-insert-markup "
\n" "

" (planner-note-anchor info) ". " (planner-note-title info) (if (planner-note-timestamp info) (concat ": " (planner-note-timestamp info)) "") "" (if (and (not (string-match planner-date-regexp (planner-note-page info))) (planner-note-link-text info)) (concat " (" (sacha/muse-publish-markup-string (planner-note-link-text info)) ")") "") "

\n") (planner-insert-markup "
\n") (insert (planner-note-body info)) (planner-insert-markup "\n
") (planner-insert-markup ;", " ;(format "" id) ; "
" "Tell me what you think! -- Back to top
\n")))))) ;;_+ Schedule next undated task from same project ;; For Jody Klymak (defun sacha/planner-seek-next-unfinished-and-undated-task () "Move point to the next unfinished task on this page. Return nil if not found, the task if otherwise." (interactive) (let (task-info) (while (and (not task-info) (re-search-forward "^#[A-C][0-9]*\\s-+[^CX]\\s-+" nil t)) (setq task-info (planner-current-task-info)) (when (planner-task-date task-info) (setq task-info nil))) task-info)) (defun sacha/planner-queue-next-task (&optional task-info) "Schedule the next task for TASK-INFO or the current task for today." (interactive) (save-window-excursion (save-excursion (setq task-info (or task-info (planner-current-task-info))) (when (and task-info (planner-task-plan task-info)) (planner-find-file (planner-task-plan task-info)) (goto-char (point-min)) (if (sacha/planner-seek-next-unfinished-and-undated-task) (planner-copy-or-move-task (planner-today)) (message "No more unschedulefd tasks for %s." (planner-task-plan task-info))))))) (defun sacha/planner-schedule-next-task (old-status new-status) "Schedule next task if there are no other unfinished tasks for this project." (when (and (string= new-status "X") (not (string= old-status "X"))) (let ((task-info (planner-current-task-info)) (not-seen t)) (when (and task-info (planner-task-plan task-info) (planner-task-date task-info)) (save-window-excursion (save-excursion (when (string= (planner-task-plan task-info) (planner-task-page task-info)) (planner-jump-to-linked-task)) (goto-char (point-min)) (while (and not-seen (re-search-forward "^#[A-C][0-9]*\\s-+[^CX]\\s-+" nil t)) (let ((current (planner-current-task-info))) (when (string= (planner-task-plan task-info) (planner-task-plan current)) (setq not-seen nil)))))) (when not-seen (sacha/planner-queue-next-task task-info))))) t) ;(add-hook 'planner-mark-task-hook 'sacha/planner-schedule-next-task) ;;;_+ Keep track of what I'm supposed to be doing ;; I've bound sacha/planner-what-am-i-supposed-to-be-doing to F9 F9. I ;; start out by clocking into the task (use planner-timeclock.el and ;; C-c TAB to mark a task as in progress). Then, when I find myself ;; getting distracted, I hit F9 F9 to see my current task in the ;; minibuffer. C-u F9 F9 jumps back to the task so that I can either ;; mark it as postponed. M-x planner-task-pending (bound to C-c C-p in ;; my local config) and M-x planner-task-done (C-c C-x) both clock out ;; of the task. If I want to jump back to the previous window ;; configuration from that planner page, I can just hit F9 F9 again. (defvar sacha/window-register "w" "Register for jumping back and forth between planner and wherever I am.") (defvar sacha/planner-current-task nil "Current task info.") (defadvice planner-task-in-progress (after sacha activate) "Keep track of the task info." (setq sacha/planner-current-task (planner-current-task-info))) (defadvice planner-task-done (before sacha activate) "Report task." (when planner-timeclock-current-task (message "%s %s" ;; Minutes so far (timeclock-seconds-to-string (timeclock-last-period)) planner-timeclock-current-task))) (defun sacha/planner-what-am-i-supposed-to-be-doing (&optional prefix) "Make it easy to keep track of what I'm supposed to be working on. If PREFIX is non-nil, jump to the current task, else display it in a message. If called from the plan page, jump back to whatever I was looking at." (interactive "P") (if planner-timeclock-current-task (progn (message "%s %s" ;; Minutes so far (timeclock-seconds-to-string (timeclock-last-period)) planner-timeclock-current-task) (if (and (string= (planner-task-page sacha/planner-current-task) (planner-page-name)) (window-configuration-p sacha/window-register)) (jump-to-register sacha/window-register) (if (null prefix) (frame-configuration-to-register sacha/window-register) (planner-find-file (planner-task-page sacha/planner-current-task)) (planner-find-task sacha/planner-current-task)))) (if prefix (planner-goto-today) (message "No current task. HEY!")))) ;;;_+ Removing task numbers (defun sacha/planner-strip-task-numbers () (interactive) (while (re-search-forward "^#.\\([0-9]+\\)\\s-+.\\s-+" nil t) (replace-match "" t t nil 1)) (planner-align-tasks)) ;;;_+ Marking up IDs as images ;; (defun planner-id-image (id) ;; "Return the image to mark up ID as, or nil if none." ;; (save-match-data (when (string-match "Tasks" id) "~/notebook/pics/screen/id-small.png"))) ;; (defun planner-id-highlight-images (beg end &optional verbose) ;; "Highlight IDs as pictures from BEG to END. ;; VERBOSE is ignored." ;; (goto-char beg) ;; (while (re-search-forward "{{[^}]+}}" end t) ;; (let ((image (planner-id-image (match-string 0)))) ;; (when image ;; (emacs-wiki-inline-image (match-beginning 0) ;; (match-end 0) ;; image ;; (match-string 0)))))) ;; (add-hook 'planner-mode-hook ;; (lambda () (add-hook 'emacs-wiki-highlight-buffer-hook ;; 'planner-id-highlight-images))) ;;;_+ Fancy task sorting: idea and base code from johnsu01 on 2005.02.18. ;; This code allows you to sort your tasks based on regular expressions. ;; Try it out with ;; ;; C-u M-x sacha/planner-score-sort-tasks RET some-regexp-matching-tasks-to-be-raised RET ;; ;; If you like the effects and want to keep a whole bunch of sorting ;; rules so that you can call M-x sacha/planner-score-sort-tasks ;; without any arguments, modify the sacha/planner-score-rules ;; variable. ;; ;; If you want this to become your default sorting algorithm, ;; (setq planner-sort-tasks-key-function 'sacha/planner-score-tasks-key) ;; ;; If you want it to trigger only on some pages but not on others, see ;; the `planner-sort-tasks-basic' function for inspiration. ;; ;; I hope this code shows how easy it is to tweak task sorting. =) ;; It's also handy for quickly pulling up certain tasks, as the regular ;; M-x planner-sort-tasks will leave some semblance of the old order in. (defvar sacha/planner-score-rules '(("patch" . 100) ("bug" . 100)) "*Alist of planner scoring rules of the form (regexp . score-value). Tasks with higher scores are listed first.") (defun sacha/planner-score-tasks-key () "Sort tasks by the rules in `sacha/planner-score-rules'." (let ((score 0) (case-fold-search t) (line (buffer-substring-no-properties (line-beginning-position) (line-end-position)))) (mapc (lambda (item) (when (string-match (car item) line) (setq score (- score (cdr item))))) sacha/planner-score-rules) score)) (defun sacha/planner-score-sort-tasks (&optional new-rule) "Sort tasks by `sacha/planner-score-rules' or NEW-RULE. If called interactively, prompt for NEW-RULE. If NEW-RULE is non-nil, tasks matching that regexp are raised. If not, tasks are sorted according to `sacha/planner-score-rules'." (interactive (list (read-string "Task regexp: "))) (let ((planner-sort-tasks-key-function 'sacha/planner-score-tasks-key) (sacha/planner-score-rules (if new-rule (list (cons new-rule 1)) sacha/planner-score-rules))) (planner-sort-tasks))) ;;;_+ 2005.03.14 Don't resolve e-mail addresses (defun sacha/planner-bbdb-resolve-url (id) "Replace ID with the blog, web or e-mail address of the BBDB record." (save-match-data (when (string-match "^bbdb:/+" id) (setq id (replace-match "" t t id))) (let ((record (car (bbdb-search (bbdb-records) id id id)))) (and record (or (bbdb-record-getprop record 'blog) (bbdb-record-getprop record 'web) (bbdb-record-getprop record 'www)))))) (defalias 'planner-bbdb-resolve-url 'sacha/planner-bbdb-resolve-url) ;;;_+ 2005.03.24 Random fortunes (defvar sacha/fortune-file "/usr/share/games/fortunes/linuxcookie" "*Base file for fortune.") (defvar sacha/fortune-command "/usr/games/fortune" "The fortune executable.") (defun sacha/fortune (&optional file) "Return a fortune as a string." (interactive) (let ((line (shell-command-to-string (concat sacha/fortune-command " " (or file sacha/fortune-file))))) (kill-new line) (message line) line)) (defun sacha/planner-day-page-template () "Day page template for Sacha." (let ((date (planner-filename-to-calendar-date (planner-page-name)))) (insert "Headlines for " (calendar-day-name date) ": * Tasks * Schedule * Notes * Contact "))) (setq planner-day-page-template 'sacha/planner-day-page-template) ;;;_+ No more line-breaking for tasks. Thanks to Keith Amidon (add-hook 'planner-mode-hook (lambda () (setq auto-fill-inhibit-regexp "^#[ABC] +[_oX].*") (setq truncate-lines t))) ;;;_+ 2005.04.07 Livejournal (defun sacha/planner-lj-browse (url) "Browse the LiveJournal user named by URL (lj:sachachua, for example)." (when (string-match "lj:\\(.+\\)" url) (browse-url (concat "http://www.livejournal.com/users/" (match-string 1 url))))) (defun sacha/planner-lj-resolve (url) "Browse the LiveJournal user named by URL (lj:sachachua, for example)." (when (string-match "lj:\\(.+\\)" url) (concat "http://www.livejournal.com/users/" (match-string 1 url)))) ;(planner-add-protocol "lj:" 'sacha/planner-lj-browse 'sacha/planner-lj-resolve) ;;;_+ 2005.04.08 w3m mirror (defun sacha/planner-w3m-annotation-from-mirror () "Return an annotation to a mirror, if it exists." (when (and (boundp 'sacha/w3m-mirror-directory) sacha/w3m-mirror-directory (eq major-mode 'w3m-mode)) (let ((url w3m-current-url) file escaped) (when (string-match "^\\([^:]+\\):[^/]*//" url) (setq file (substring url (match-end 0))) (setq url (replace-match "mirror://" nil t url))) (if (file-exists-p (expand-file-name file sacha/w3m-mirror-directory)) (concat (planner-make-link w3m-current-url w3m-current-title t) " " (planner-make-link url "mirror" t)) (planner-make-link w3m-current-url w3m-current-title t))))) (defun sacha/planner-w3m-mirror-browse-url (url) "Browse to the mirrored URL." (when (string-match "^mirror://" url) (setq url (replace-match (concat "file://" (file-name-as-directory (expand-file-name sacha/w3m-mirror-directory))) nil t url)) (setq url (planner-replace-regexp-in-string "\\?" "%3F" url)) (let ((w3m-local-find-file-function nil)) (browse-url url)))) (require 'planner-w3m) (add-to-list 'planner-annotation-functions 'planner-w3m-annotation-from-w3m) (add-to-list 'planner-annotation-functions 'sacha/planner-w3m-annotation-from-mirror) ;(planner-add-protocol "mirror:" 'sacha/planner-w3m-mirror-browse-url nil) ;;;_+ Special markup for tasks (defun sacha/planner-markup-tasks () "Mark up tasks as a table." (goto-char (point-min)) (when (re-search-forward planner-task-regexp nil t) (goto-char (line-beginning-position)) (planner-insert-markup "\n
Priorities - A: high, B: medium, C: low; Status - _: unfinished, X: finished, C: cancelled, P: pending, o: in progress, >: delegated.
\n") (while (re-search-forward planner-task-regexp nil t) (goto-char (line-beginning-position)) (planner-insert-markup "\n") (while (looking-at "^#\\([A-C]\\)\\([0-9]*\\)\\s-+\\(.\\)\\s-+\\(.+\\)") (let* ((info (planner-current-task-info)) (priority (planner-task-priority info)) (number (planner-task-number info)) (status (planner-task-status info)) (text (planner-task-description info)) (link (planner-task-link-text info)) (status-style (cond ((string= status "_") "task_") ((string= status "o") "tasko") ((string= status ">") "taskd") ((string= status "P") "taskp") ((string= status "X") "taskX") ((string= status "C") "task_cancelled") (t "task")))) (delete-region (line-beginning-position) (min (point-max) (1+ (line-end-position)))) (planner-insert-markup (format "\n"))) (planner-insert-markup "
%s%s" (cond ((string= priority "A") "taskA") ((string= priority "B") "taskB") ((string= priority "C") "taskC") (t "task")) (concat priority number) status status-style)) (insert text (if link (format " (%s)" link) "")) (planner-insert-markup "
\n")))) (defun sacha/planner-fix-examples () (goto-char (point-min)) (while (re-search-forward "" nil t) (delete-region (match-beginning 0) (match-end 0)) (let ((start (point))) (if (muse-goto-tag-end "example" nil) (delete-region (match-beginning 0) (point))) (muse-publish-example-tag start (point))))) (defun sacha/planner-before-markup () "Mark up , tasks and notes." (sacha/planner-skip-private) (goto-char (point-min)) (when (re-search-forward "" nil t) (sacha/planner-publish-notes-tag (match-beginning 0) (match-end 0))) (goto-char (point-min)) (while (re-search-forward "" nil t) (let ((begin (match-beginning 0)) (end (progn (re-search-forward "" nil t) (match-end 0)))) (delete-region begin end) (insert "..."))) (goto-char (point-min)) (sacha/planner-markup-notes) (goto-char (point-min)) (sacha/planner-markup-tasks)) (defadvice planner-publish-markup-note (around sacha activate) "Do nothing.") (defadvice planner-publish-markup-task (around sacha activate) "Do nothing.") ;;;_+ 2005.04.20: Recent posts (defun sacha/planner-add-recent () "Add the current note to the list of recent entries, trimmed." (interactive) (let* ((info (planner-current-note-info)) (url (concat "http://sachachua.com/notebook/wiki/" (planner-note-page info) ".php#anchor-" (planner-note-anchor info))) (title (planner-note-title info))) (when info (when (string-match (concat "\\s-*(\\(" sacha/muse-link-regexp (regexp-quote planner-multi-separator) "\\)*" sacha/muse-link-regexp ")\\s-*$") title) (setq title (replace-match "" nil nil title))) (with-current-buffer (find-file-noselect "/home/sacha/notebook/wiki/include/footer.inc.php") (goto-char (point-min)) (when (re-search-forward "" nil t) (save-restriction (narrow-to-region (1+ (line-end-position)) (progn (re-search-forward "" nil t) (line-beginning-position))) (goto-char (point-min)) (planner-insert-markup "- " title "
\n") (forward-line 14) (delete-region (point) (point-max)) (save-buffer)))))) nil) ;;(eval-after-load 'remember-planner ;; '(add-hook 'remember-planner-append-hook 'sacha/planner-add-recent t)) (eval-after-load 'remember-planner '(defadvice remember-planner-append (around sacha activate) "Do not create cyclic tasks." (let ((planner-cyclic-create-tasks-p nil)) ad-do-it))) ;;;_+ Don't prompt for a date; always schedule tasks onto today ;(defun sacha/planner-read-task () ; "Return a list of information for a task. ;Universal prefix means don't schedule the task onto today." ; (list ; (read-string "Describe task: ") ; (if current-prefix-arg ; (planner-read-date) ; (planner-today)) ; (when planner-use-plan-pages ; (let ((planner-default-page ; (if (and (planner-derived-mode-p 'planner-mode) ; (planner-page-name) ; (not (string-match planner-date-regexp ; (planner-page-name)))) ; (planner-page-name) ; planner-default-page))) ; (planner-read-non-date-page ; (planner-file-alist)))) ; planner-default-task-status)) (setq planner-default-task-priority "B") (defun sacha/planner-read-task () (let ((planner-expand-name-favor-future-p t)) (list (read-string "Describe task: ") (if current-prefix-arg (planner-read-date) (planner-today)) nil planner-default-task-status))) (defalias 'planner-read-task 'sacha/planner-read-task) ;;;_+ Delete this page (defun sacha/planner-delete-page () "Delete this page and the published file." (interactive) (condition-case nil (delete-file (muse-publish-output-file)) (error nil)) (condition-case nil (delete-file (buffer-file-name)) (error nil)) (kill-buffer (current-buffer)) (with-planner (muse-project-file-alist nil t))) ;;;_+ Automatically cross-reference new tasks onto TaskPool (setq planner-multi-copy-tasks-to-page "TaskPool") ;;;_+ Always fix tasks (defun peter/planner-fix-tasks-after-marking (old-status new-status) "Always fix tasks." (unless (string= old-status new-status) (save-window-excursion (let ((info (planner-current-task-info))) (planner-fix-tasks) (when (planner-task-link-text info) (if (string-match planner-multi-separator (planner-task-link-text info)) (let ((links (planner-multi-task-link-as-list info))) (while links (planner-find-file (car links)) (planner-fix-tasks) (setq links (cdr links)))) (planner-find-file (planner-task-link info)) (planner-fix-tasks))) (planner-find-file (planner-task-page info)) (planner-find-task info)))) t) ;;;_+ Sacha's funky task sorting (setq planner-sort-tasks-key-function 'sacha/planner-sort-tasks-key) (defun sacha/planner-sort-tasks-key () "Provide old sorting behavior. Day pages sort by status and priority. Plan pages sort by date, status and priority." (if planner-on-date-page (sacha/planner-sort-tasks-basic) (sacha/planner-sort-tasks-by-date))) (defun sacha/planner-sort-tasks-basic () "Sort tasks by time (@1030, etc), priority, and status (oP_>XC)." (let* ((info (planner-current-task-info)) (status (aref (planner-task-status info) 0))) (concat (if (or (eq status ?X) (eq status ?C)) "Z" ; Completed and cancelled tasks go last " ") ;; Sort by time (or (and (string-match "@[0-9][0-9]:[0-9][0-9]" (planner-task-description info)) (match-string 0 (planner-task-description info))) "@9999") ;; Sort by status: in progress, postponed, ;; open, delegated, completed, cancelled (cond ((eq status ?o) "1") ((eq status ?P) "2") ((eq status ?>) "4") ((eq status ?X) "5") ((eq status ?C) "6") (t "3")) ;; A, B, C (planner-task-priority info)))) (setq planner-sort-undated-tasks-equivalent "0000.00.00") (defun sacha/planner-sort-tasks-by-date () "Sort undated and unscheduled tasks first, then sort by status and priority." (skip-chars-forward "#ABC") (let ((ch (char-before)) status) (skip-chars-forward "0123456789 ") (setq status (char-after)) (goto-char (line-end-position)) (skip-chars-backward "]) ") (format "%10s%1c%1c" (let ((date (or (planner-task-date (planner-current-task-info)) planner-sort-undated-tasks-equivalent))) (if (or (= status ?X) (= status ?C)) (sacha/planner-invert-date date) date)) (cond ((= status ?o) ?1) ((= status ?X) ?3) ((= status ?C) ?4) (t ?2)) ch))) (defun sacha/planner-invert-date (date) "Reverse the date in the sorting order." (mapconcat (lambda (ch) (if (string= ch ".") ch (number-to-string (- 9 (string-to-number ch))))) (split-string date "" t) "")) ;;;_+ Specify task priority on creation (defadvice planner-create-task-from-buffer (around sacha activate) "Change the priority if specified. You can set the priority of a task during creation by starting the task description with #A, #B, or #C. This changes the default task status." (when (string-match "^#\\([ABC]\\)[ \t]" title) (setq planner-default-task-priority (match-string 1 title)) (setq title (substring title (match-end 0)))) (if (string-match "^\\s-*\\([_X>Po]\\)\\s-+" title) (let ((planner-default-task-status (match-string 1 title))) (setq title (substring title (match-end 0))) ad-do-it) ad-do-it)) ;;;_+ 2005.07.01 Technorati (defun sacha/planner-technorati-resolve (url) "Add a link to the technorati tag named by URL." (when (string-match "tag:\\(.+\\)" url) (concat "http://www.technorati.com/tag/" (match-string 1 url)))) (defun sacha/planner-technorati-browse (url) "Browse the Technorati tag named by URL (technorati:emacs, for example)." (when (string-match "tag:\\(.+\\)" url) (browse-url (sacha/planner-technorati-resolve url)))) ;(planner-add-protocol "tag:" 'sacha/planner-technorati-browse 'sacha/planner-technorati-resolve) (defun sacha/planner-technorati-tag (beg end &optional attrs) (let ((list (cdr (assoc "s" attrs)))) (planner-insert-markup "On Technorati: " (mapconcat (lambda (string) (concat "" string "")) (split-string list ",") ", ")))) ;(add-to-list 'muse-publish-markup-tags '("tag" nil t nil sacha/planner-technorati-tag)) (defun sacha/planner-private-tag (beg end &optional attrs) (interactive) (let ((inhibit-read-only t)) (delete-region beg end)) (planner-insert-markup "...")) (add-to-list 'muse-publish-markup-tags '("private" t nil nil sacha/planner-private-tag)) ;;;_+ Publishing (defun sacha/muse-publish-this-page () "Save and publish this page." (interactive) (unless muse-publishing-p (when (or (interactive-p) (and (not (string-match ".rdf" (buffer-file-name))) (not (muse-project-private-p (buffer-file-name))))) (let ((new (not (muse-project-page-file (planner-current-file) planner-project)))) (save-buffer) (when new (muse-project-file-alist nil t)) (sacha/muse-project-publish-file))))) (define-key planner-mode-map (kbd "C-c C-.") 'sacha/muse-publish-this-page) (add-hook 'planner-mode-hook (lambda () (add-hook 'after-save-hook 'sacha/muse-publish-this-page))) ;;;_+ Auto-schedule tasks onto today before marking them as done (defun sacha/planner-track-finished-tasks (old-status new-status) "Automatically reschedule tasks onto today before marking them as done. Add this to `planner-mark-task-hook'." (when (string= new-status "X") (let ((info (planner-current-task-info))) (unless (string= (planner-task-date info) (planner-today)) (planner-copy-or-move-task (planner-today) t) (when (string-match planner-date-regexp (planner-page-name)) (planner-find-file (planner-today)) (planner-find-task info))))) t) ;(add-hook 'planner-mark-task-hook 'sacha/planner-track-finished-tasks) ;(remove-hook 'planner-mark-task-hook 'sacha/planner-track-finished-tasks) ;;;_+ 2005.04.07 Podcasts (defun sacha/planner-podcast-browse (url) "Play the given podcast." (when (string-match "pod:\\(.+\\)" url) (shell-command (concat "play ~/notebook/podcast/" (planner-replace-regexp-in-string "#" "-" (match-string 1 url) ".mp3 &"))))) (defun sacha/planner-podcast-resolve (url) "Link to the given podcast." (when (string-match "pod:\\(.+\\)" url) (concat "http://sachachua.com/notebook/podcast/" (planner-replace-regexp-in-string "#" "-" (match-string 1 url)) ".mp3"))) (defun sacha/planner-podcast-tag (beg end &optional attrs) (interactive) (let ((filename (cdr (assoc "file" attrs)))) (planner-insert-markup "" (cdr (assoc "title" attrs)) " \"MP3"))) (add-to-list 'muse-publish-markup-tags '("pod" nil t sacha/planner-podcast-tag)) ;;(planner-add-protocol "pod" 'sacha/planner-podcast-browse 'sacha/planner-podcast-resolve) ;;;_+ Do not add cyclic tasks to pool (defadvice planner-cyclic-create-task-maybe (around sacha activate) "Do not add cyclic tasks to TaskPool." (let ((planner-multi-copy-tasks-to-page nil)) ad-do-it)) ;;;###autoload (defun sacha/planner-multi-remove-task-from-pool () "Remove tasks from TaskPool." (interactive) (with-planner-update-setup (let ((info (planner-current-task-info))) (when (planner-task-link-text info) ;; If it is linked to TaskPool _and_ at least one other thing (cond ((string-match planner-multi-separator (planner-task-link-text info)) (let ((remove-from (mapcar 'planner-link-base (planner-multi-split planner-multi-copy-tasks-to-page))) new-links) (setq new-links (delq nil (mapcar (lambda (item) (unless (member (planner-link-base item) remove-from) (planner-link-base item))) (planner-multi-task-link-as-list info)))) (save-excursion (planner-replan-task (mapconcat 'identity new-links planner-multi-separator))) ;; Make sure we are on the same task (when (string= (planner-page-name) planner-multi-copy-tasks-to-page) (planner-find-file (car new-links)) (planner-find-task info)))) ;; Else if it has a date and is linked to TaskPool ((and (planner-task-date info) (string= (planner-task-plan info) planner-multi-copy-tasks-to-page) (save-excursion (planner-replan-task nil)) (when (string= (planner-page-name) planner-multi-copy-tasks-to-page) (planner-find-file (planner-task-date info)) (planner-find-task info))))))))) ;;;_+ Quickly show output file (defun sacha/planner-show-output-file () "Visit published file." (interactive) (sacha/muse-project-publish-file) (find-file-other-window (muse-publish-output-file (buffer-file-name) (muse-style-element :path (car (cddr muse-current-project))) (car (cddr muse-current-project))))) ;;;_+ Muse hack to make tag work again (require 'assoc) (aput 'planner-publish-markup-tags "notes" '(nil nil sacha/planner-publish-notes-tag)) (setq sectionalize-markup-tagname nil) (setq planner-publish-markup-regexps '((1375 "^#\\([A-C]\\)\\([0-9]*\\)\\s-*\\([_oX>CP]\\)\\s-*\\(.+\\)" 0 task) (1380 "^\\.#[0-9]+\\s-*" 0 note))) (defvar sacha/muse-link-regexp (concat "\\(?:\\(?:" muse-explicit-link-regexp "\\)\\|\\(?:\\(\\(?:[[:upper:]][[:lower:]]+\\)\\(?:[[:upper:]][[:lower:]]+\\)+\\)" "\\)\\|\\(?:[0-9][0-9][0-9][0-9]\\.[0-9][0-9]\\.[0-9][0-9]\\)\\)") "Regexp that matches extended links and wiki words.") (defun sacha/planner-publish-notes-tag (beg end) "Replace the region BEG to END with the notes for this page. Idiosyncratic." (delete-region beg end) (let ((case-fold-search nil) (date-page-p (string-match planner-date-regexp (planner-page-name)))) (mapcar (lambda (item) (when (string-match (if (featurep 'planner-multi) (concat " *(\\(" sacha/muse-link-regexp planner-multi-separator "\\)*" sacha/muse-link-regexp ") *$") (concat "\\s-*(\\(" sacha/muse-link-regexp "\\))\\s-*$")) (cdr item)) (setcdr item (replace-match "" nil t (cdr item)))) (insert " 1. ") (planner-insert-markup (format "%s\n" (planner-page-name) (car item) (cdr item)))) (planner-notes-get-headlines)))) ;;;_+ Emacspeak (defadvice muse-next-reference (after emacspeak pre act comp) "Provide additional feedback." (message "%s" (if (looking-at muse-explicit-link-regexp) (or (match-string 2) (match-string 1)) (match-string 0)))) (defadvice muse-previous-reference (after emacspeak pre act comp) "Provide additional feedback." (message "%s" (if (looking-at muse-explicit-link-regexp) (or (match-string 2) (match-string 1)) (match-string 0)))) ;;;_+ Convenience (defun sacha/planner-marked-images-as-kill () "Return a list of images ready to be inserted into a wiki page." (interactive) (kill-new (mapconcat (lambda (item) (planner-make-link (file-relative-name item planner-publishing-directory))) (dired-get-marked-files) "\n"))) ;;;_+ I like using =) smilies! (muse-configure-highlighting 'muse-colors-markup (delete (list (concat "=[^" muse-regexp-blank "=]") 61 'muse-colors-verbatim) muse-colors-markup)) ;;;_+ Undate tasks (defun khj/planner-unschedule-finished-task (old-status new-status) "Remove planned tasks from day pages after completion. Add this to `planner-mark-task-hook'." (when (string= new-status "X") (let ((info (planner-current-task-info))) (when (planner-task-plan info) (planner-copy-or-move-task nil t) (planner-find-file (planner-task-plan info)) (planner-find-task info)))) t) ;(add-hook 'planner-mark-task-hook 'khj/planner-unschedule-finished-task) ;;;_+ 2006.01.06: Publishing notes as XML (defun sacha/planner-add-recent () "Add the current note to the list of recent entries, trimmed." (interactive) (let* ((info (planner-current-note-info)) (url (concat "http://sachachua.com/notebook/wiki/" (planner-note-page info) ".php#anchor-" (planner-note-anchor info))) (title (planner-note-title info))) (when info (when (string-match (concat "\\s-*(\\(" sacha/muse-link-regexp (regexp-quote planner-multi-separator) "\\)*" sacha/muse-link-regexp ")\\s-*$") title) (setq title (replace-match "" nil nil title))) (with-current-buffer (find-file-noselect "/home/sacha/notebook/wiki/include/footer.inc.php") (goto-char (point-min)) (when (re-search-forward "" nil t) (save-restriction (narrow-to-region (1+ (line-end-position)) (progn (re-search-forward "" nil t) (line-beginning-position))) (goto-char (point-min)) (planner-insert-markup "- " title "
\n") (forward-line 24) (delete-region (point) (point-max)) (save-buffer)))))) nil) (eval-after-load 'remember-planner '(add-hook 'remember-planner-append-hook 'sacha/planner-add-recent t)) ;;;_+ Fix RSS, which keeps breaking on me (defun sacha/planner-current-note-info (&optional include-body) "Parse the current note and return the note information as a list. The list is of the form (PAGE ANCHOR TITLE TIMESTAMP LINK BODY TAGS). TAGS is a space-separated list of keywords. If INCLUDE-BODY is non-nil, the list will include the body of the note." (save-excursion (save-restriction (when (planner-narrow-to-note) (goto-char (point-min)) (when (looking-at "^\\.#\\([0-9]+\\)\\s-+\\(.+\\)") (let ((anchor (planner-match-string-no-properties 1)) (title (planner-match-string-no-properties 2)) timestamp link tags) (when (string-match (concat "\\s-+(\\(" (if (featurep 'planner-multi) (concat "\\(" muse-explicit-link-regexp "\\)" "\\(" (regexp-quote planner-multi-separator) muse-explicit-link-regexp "\\)*") muse-explicit-link-regexp) "\\))\\s-*$") title) (setq link (planner-match-string-no-properties 1 title)) (setq title (replace-match "" nil t title))) (when (string-match "\\s-*\\([0-9]+:[0-9][0-9]\\)" title) (setq timestamp (planner-match-string-no-properties 1 title)) (setq title (replace-match "" nil t title))) (save-excursion (goto-char (point-min)) (when (re-search-forward "" nil t) (setq tags (split-string (planner-match-string-no-properties 1) ",")))) (list (planner-page-name) anchor title timestamp link (and include-body (buffer-substring-no-properties (planner-line-end-position) (point-max))) tags))))))) (defalias 'planner-current-note-info 'sacha/planner-current-note-info) (defsubst sacha/planner-note-tags (info) (elt info 6)) (defun sacha/planner-publish-markup-note-rss () "Replace note with RSS 2.0 representation of note data." (save-restriction (narrow-to-region (save-excursion (beginning-of-line) (point)) (or (save-excursion (and (re-search-forward "\\|" nil t) (match-beginning 0))) (point-max))) (let ((info (planner-current-note-info t)) (inhibit-read-only t) tags) (delete-region (point-min) (point-max)) (planner-insert-markup "\n") (planner-insert-markup "" (muse-publish-escape-specials-in-string (planner-note-title info)) "\n") (planner-insert-markup "" (concat planner-rss-base-url (muse-page-name) ".php#anchor-" (planner-note-anchor info)) "\n") (planner-insert-markup "" (concat planner-rss-base-url (muse-page-name) ".php#anchor-" (planner-note-anchor info)) "\n") (when (planner-note-body info) (let ((categories (sacha/planner-note-tags info)) (muse-publish-markup-tags muse-publish-markup-tags)) ;; special processing for Technorati tags (add-to-list 'muse-publish-markup-tags '("tag" nil t nil sacha/planner-technorati-tag)) (planner-insert-markup "\n") (while categories (planner-insert-markup "" (car categories) "\n") (setq categories (cdr categories))))) (when (planner-note-date info) (planner-insert-markup "" (let ((system-time-locale "C") (timestamp (planner-note-timestamp info)) (date (planner-filename-to-calendar-date (planner-note-date info))) (minutes) (hour) (day) (month) (year)) (format-time-string "%a, %d %b %Y %T %Z" (when (string-match "\\([0-9]+\\):\\([0-9]+\\)" timestamp) (let ((hour (string-to-number (match-string 1 timestamp))) (minutes (string-to-number (match-string 2 timestamp))) (month (nth 0 date)) (day (nth 1 date)) (year (nth 2 date))) (encode-time 0 minutes hour day month year))))) "\n")) (planner-insert-markup "\n")))) (defalias 'planner-publish-markup-note-rss 'sacha/planner-publish-markup-note-rss) ;;;_+ Keep track of sent messages (defun sacha/planner-gnus-track-sent () "Add this to `message-sent-hook' to keep track of messages sent on your daily page. Result: Adds it to * Contact." ;; Grab the header (let* ((mail-link (concat "gnus://" (or gnus-newsgroup-name "nnml:mail.misc") "/" (planner-gnus-get-message-id))) (bbdb-get-only-first-address-p nil) (addresses (split-string (planner-gnus-get-address "To") ", *")) text days) (goto-char (point-min)) (when (re-search-forward "In reply to .+ \\([0-9]+ days ago\\|yesterday\\|today\\)" nil t) (setq days (match-string 1))) (setq text (concat (planner-make-link mail-link (if days "Reply to" "E-mail to")) " " (mapconcat (lambda (address) (let (rec) ;; Look up record (setq addr (mail-extract-address-components address)) (if addr (setq rec (apply 'bbdb-search-simple addr))) (if rec (planner-make-link (concat "bbdb://" (planner-replace-regexp-in-string " " "." (bbdb-record-name rec))) (bbdb-record-name rec)) (when (string-match "^[^@]*" address) (match-string 0 address))))) addresses ", ") (if days (concat " - sent " days) ""))) ;; ": " ;; (message-fetch-field "Subject")))) (save-window-excursion (save-excursion (planner-goto-today) (planner-seek-to-first "Contact") (insert " 1. " text "\n")))) nil) (add-hook 'message-mode-hook (lambda () (add-hook 'message-sent-hook 'sacha/planner-gnus-track-sent))) ;;;_+ Make it easier to refer to blog entries (defun sacha/yank-blog-reference () (interactive) (let ((info (planner-current-note-info)) s) (setq s (format "%s\nhttp://sachachua.com/notebook/wiki/%s.php#anchor-%s\n\n" (planner-note-title info) (planner-note-page info) (planner-note-anchor info))) (kill-new s) s)) (defun sacha/yank-blog-reference-for-summary () (interactive) (let ((info (planner-current-note-info)) s) (setq s (format "1. [[http://sachachua.com/notebook/wiki/%s.php#anchor-%s][%s#%s: %s]]\n" (planner-note-page info) (planner-note-anchor info) (planner-note-page info) (planner-note-anchor info) (planner-note-title info))) (kill-new s) s)) (setq muse-xml-markup-specials nil) ;;;_+ Link and anchor- (defun sacha/muse-publish-classify-url (target) "Transform anchors and get published name, if TARGET is a page. The return value is a cons cell. The car is the type of link, the cadr is the page name, and the cddr is the anchor." (save-match-data (cond ((or (null target) (string= target "")) nil) ((string-match muse-url-regexp target) (cons 'url (cons target nil))) ((string-match muse-image-regexp target) (cons 'image (cons target nil))) ((string-match muse-file-regexp target) (cons 'file (cons target nil))) ((string-match "#" target) (if (eq (aref target 0) ?\#) (if (string-match "#[0-9]" target) (cons 'anchor-ref (cons nil (concat "anchor-" (substring target 1)))) (cons 'anchor-ref (cons nil (substring target 1)))) (cons 'link-and-anchor (cons (muse-publish-link-name (substring target 0 (match-beginning 0))) (let ((anchor (substring target (match-end 0)))) (if (string-match "^[0-9]" anchor) (concat "anchor-" anchor) anchor)))))) (t (cons 'link (cons (muse-publish-link-name target) nil)))))) (defadvice muse-publish-classify-url (around sacha activate) (setq ad-return-value (sacha/muse-publish-classify-url (ad-get-arg 0)))) ;;;_+ Word count (defun sacha/count-words-in-region (start end) "Return the number of words in the region." (let ((text (buffer-substring-no-properties start end))) (with-temp-buffer (insert text) (shell-command-on-region (point-min) (point-max) "wc -w" nil t) (string-to-number (buffer-string))))) (defun sacha/count-words-in-string (text) "Return the number of words in STRING." (with-temp-buffer (insert text) (shell-command-on-region (point-min) (point-max) "wc -w" nil t) (string-to-number (buffer-string)))) (defun sacha/planner-notes-count-words (&optional limit) "Return number of words in blog entries today. If LIMIT is non-nil, return only that many from the top." (when (stringp limit) (setq limit (string-to-number limit))) (let ((total 0)) (save-excursion (save-restriction (widen) (goto-char (point-min)) (while (and (re-search-forward "^.\\(#[0-9]+\\)\\s-+\\(.+\\)" nil t) (if limit (> limit 0) t)) (setq total (+ total (sacha/count-words-in-string (planner-note-body (planner-current-note-info t))))) (if limit (setq limit (1- limit)))))) total)) (defun sacha/planner-notes-get-headlines (&optional limit) "Return note headlines on the current page. If LIMIT is non-nil, return only that many from the top." (when (stringp limit) (setq limit (string-to-number limit))) (let (headlines) (save-excursion (save-restriction (widen) (goto-char (point-min)) (while (and (re-search-forward "^.\\(#[0-9]+\\)\\s-+\\(.+\\)" nil t) (if limit (> limit 0) t)) (let ((info (planner-current-note-info t))) (setq headlines (cons (cons (concat "#anchor-" (planner-note-anchor info)) (concat (planner-note-title info) " (" (number-to-string (sacha/count-words-in-string (planner-note-body info))) " words)")) headlines))) (if limit (setq limit (1- limit)))))) (nreverse headlines))) (defalias 'planner-notes-get-headlines 'sacha/planner-notes-get-headlines) (require 'planner-notes-index) ;;;_+ +tag (muse-configure-highlighting 'muse-colors-markup (cons (list "+[-\\.A-Za-z0-9]+" 61 'sacha/muse-colors-keyword) muse-colors-markup)) (defface sacha/muse-keyword '((((class color) (background light)) (:foreground "slate gray")) (((class color) (background dark)) (:foreground "gray"))) "Face for tag/keyword text." :group 'muse-colors) (defun sacha/muse-colors-keyword () (let ((start (match-beginning 0)) multiline) (unless (eq (get-text-property start 'invisible) 'muse) ;; beginning of line or space or symbol (when (or (= start (point-min)) (eq (char-syntax (char-before start)) ?\ ) (memq (char-before start) '(?\- ?\[ ?\< ?\( ?\' ?\` ?\" ?\n))) (let ((pos (point))) (skip-chars-forward "-\\.A-Za-z0-9\n" end) (setq pos (min (1+ (point)) (point-max))) (add-text-properties (1+ start) (point) '(face sacha/muse-keyword))) (goto-char pos))))) ;;;_+ Snooze (defvar sacha/planner-snooze-date "+1" "*Snooze date.") (defun sacha/planner-snooze (&optional beg end) "Postpone a task until sacha/planner-snooze-date." (interactive (when current-prefix-arg (point) (mark))) (let ((planner-tasks-file-behavior nil)) (if beg (planner-copy-or-move-region beg end (planner-expand-name sacha/planner-snooze-date)) (planner-copy-or-move-task (planner-expand-name sacha/planner-snooze-date))))) (defun sacha/planner-set-snooze-date (date) "Change the snooze date." (interactive (list (planner-read-date))) (setq sacha/planner-snooze-date date)) ;;;_+ Emacspeak (defun sacha/planner-speak-headlines () "Speak the headlines of the current page." (interactive) (message "%s" (mapconcat (lambda (a) (concat (replace-regexp-in-string "[^[:alpha:][:space:][:digit:]]" "" (car a)) " " (replace-regexp-in-string "[^[:alpha:][:space:][:digit:]]" "" (cdr a)))) (sacha/planner-notes-get-headlines) "\n\n"))) ;;;_+ Search (defun sacha/planner-search (phrase) "Look up something in my planner." (interactive (list (read-string "Search for: " nil nil (and (point) (mark) (buffer-substring (point) (mark)))))) (let ((default-directory "/home/sacha/notebook/plans/")) (grep (concat "grep -i -r " phrase)))) ;;;_+ Schedule TaskPool tasks onto today before completing them (defadvice planner-task-done (before sacha activate) "Schedule TaskPool tasks onto today before completing them." (let ((info (planner-current-task-info))) (when (and (equal (planner-task-plan info) "TaskPool") (not (planner-task-date info))) (planner-copy-or-move-task (planner-today))))) ;;;_+ View in browser (require 'browse-url) (add-to-list 'browse-url-filename-alist '("/mnt/media/home/sacha/notebook/plans/\\(.+?\\).txt" . "http://sachachua.com/notebook/wiki/\\1.php")) ;;;_+ End (setq planner-carry-tasks-forward nil) (provide 'planner-config) ;;;_+ Don't link to files that aren't published (defun muse-publish-url (url &optional desc orig-url explicit) "Resolve a URL into its final form." (let (type anchor) (dolist (transform muse-publish-url-transforms) (setq url (save-match-data (when url (funcall transform url explicit))))) (if desc (setq desc (muse-publish-url-desc desc explicit)) (if orig-url (setq orig-url (muse-publish-url-desc orig-url explicit)))) (let ((target (muse-publish-classify-url url))) (setq type (car target) url (if (eq type 'image) (muse-publish-escape-specials-in-string (cadr target) 'image) (muse-publish-escape-specials-in-string (cadr target) 'url)) anchor (muse-publish-escape-specials-in-string (cddr target) 'url))) (cond ((eq type 'anchor-ref) (muse-markup-text 'anchor-ref anchor (or desc orig-url))) ((string= url "") desc) ((eq type 'image) (let ((ext (or (file-name-extension url) ""))) (setq url (muse-path-sans-extension url)) (if desc (muse-markup-text 'image-with-desc url ext desc) (muse-markup-text 'image url ext)))) ((eq type 'link-and-anchor) (if (file-exists-p (expand-file-name (muse-publish-output-name link) output-dir)) (muse-markup-text 'link-and-anchor url anchor (or desc orig-url)) (or desc orig-url))) ((and desc (string-match muse-image-regexp desc)) (let ((ext (or (file-name-extension desc) ""))) (setq desc (muse-path-sans-extension desc)) (muse-markup-text 'image-link url desc ext))) ((eq type 'link) (if (file-exists-p (expand-file-name (muse-publish-output-name link) output-dir)) (muse-markup-text 'link url (or desc orig-url)) (or desc orig-url))) (t (or (and (or desc (not (string= url orig-url))) (let ((text (muse-markup-text 'url-and-desc url (or desc orig-url)))) (and (not (string= text "")) text))) (muse-markup-text 'url url (or desc orig-url))))))) (setq muse-project-publish-private-files nil) (defun sacha/planner-index (&optional as-list exclude-private) "Display an index of all known Wiki pages." (interactive (list t t)) (let ((muse-current-project (muse-project planner-project))) (message "Generating Muse index...") (pop-to-buffer (planner-generate-index as-list exclude-private)) (goto-char (point-min)) (planner-mode) (message "Generating Muse index...done"))) ;;;_+ Appointments (require 'planner-appt) (planner-appt-use-tasks-and-schedule) (planner-appt-insinuate) (setq planner-appt-update-appts-on-save-flag t) (setq planner-appt-sort-schedule-on-update-flag t) (setq planner-appt-schedule-cyclic-behavior 'future) (setq planner-appt-task-use-appointments-section-flag t) (defadvice planner-appt-forthcoming-update-section-maybe (around sacha activate) (unless muse-publishing-p ad-do-it)) (defadvice planner-appt-add-appts-from-schedule (around sacha activate) (unless muse-publishing-p ad-do-it)) (defadvice planner-appt-todays-page-p (around sacha activate) (unless muse-publishing-p ad-do-it)) (require 'twit) (defun sacha/planner-scrub-private (s) (if (string-match "{{private}}.*" s) (replace-match "" t t s) s)) (defun sacha/planner-twit-done-tasks (old-status new-status) (let ((info (planner-current-task-info))) (cond ((string= new-status "X") (twit-post-function twit-update-url (concat "DONE: " (sacha/planner-scrub-private (planner-task-description info))))) ((string= new-status "o") (twit-post-function twit-update-url (concat "STARTED: " (sacha/planner-scrub-private (planner-task-description info)))))))) (add-hook 'planner-mark-task-hook 'sacha/planner-twit-done-tasks) ;(add-hook 'planner-mode-hook 'planner-appt-forthcoming-update-section-maybe) ;;;_+ 2007/10/28 Planner timeclock summaries ;(planner-timeclock-summary-proj-insinuate) ; For plan pages ;(planner-timeclock-summary-insinuate) ; For day pages ;; It's easier when both of them overwrite the same report ;(setq planner-timeclock-summary-section ; "Timeclock" ; planner-timeclock-summary-proj-section ; "Timeclock" ;;;_* Local emacs vars. ;;;Local variables: ;;;allout-layout: (* 0 : ) ;;;End: ;;; planner-config.el ends here ;;;_+ Schedule preview (defun wicked/planner-show-schedule-preview (start end) "Show the schedule preview from START to END. If called interactively, show the schedule preview for the next 7 days, or the number specified by the prefix argument." (interactive (list (planner-today) (planner-calculate-date-from-day-offset (planner-today) (or current-prefix-arg 7)))) ;; Collect the schedule sections (let ((pages (planner-get-day-pages start end)) (result "")) (save-window-excursion (with-planner-update-setup (while pages (planner-goto (caar pages)) (planner-appt-update-appt-section) (save-restriction (widen) (planner-narrow-to-section planner-appt-task-appointments-section) (goto-char (point-min)) (forward-line 1) ; skip the heading (skip-chars-forward " \t\n") (if (not (eobp)) (setq result (concat "* " (caar pages) " " (calendar-day-name (planner-filename-to-calendar-date (caar pages))) "\n" (buffer-substring (point) (point-max)) result)))) (setq pages (cdr pages))))) (with-current-buffer (get-buffer-create "*Planner Schedule Preview*") (setq muse-current-project (muse-project planner-project)) (erase-buffer) (setq buffer-read-only nil) (insert result) (planner-mode) (pop-to-buffer (current-buffer))))) ;(sacha/planner-dump-rss "~/public_html/blog-dump/" nil "2006.05.24") (defun sacha/planner-dump-rss (directory from to) (let ((pages (planner-get-day-pages from to)) (planner-rss-feed-limits nil) (planner-rss-feed-categories nil) (planner-rss-initial-contents "") buffer) (while pages (condition-case err2 (progn (planner-find-file (caar pages)) (setq buffer (current-buffer)) (unwind-protect (progn (goto-char (point-min)) (while (re-search-forward "^\\.#\\([0-9]+\\)" nil t) (save-excursion (condition-case err (progn (let ((inhibit-read-only t) (file (concat directory (caar pages) "-" (match-string 1)))) (planner-rss-add-note file) (find-file file) (save-buffer 0) (kill-buffer (current-buffer)))) (error (message "Problems processing note on %s: %s" (caar pages) (error-message-string err))))))) (kill-buffer buffer))) (error (message "Problems processing %s: %s" (caar pages) (error-message-string err2)))) (setq pages (cdr pages))))) (defun planner-narrow-to-note (&optional page note-number) "Narrow to the specified note. Widen and return nil if note is not found. If PAGE is nil, use current page. If NOTE-NUMBER is nil, use current note. Undefined behavior if PAGE is (non-nil and not today) and NOTE-NUMBER is nil." (when page (planner-goto page)) (save-excursion (let (beginning) (if note-number (progn (goto-char (point-min)) (when (re-search-forward (concat "^\\.#" note-number) nil t) (setq beginning (match-beginning 0)))) (goto-char (planner-line-end-position)) (when (re-search-backward "^\\.#[0-9]+" nil t) (setq beginning (planner-line-beginning-position)))) (when beginning ;; Search for the end (forward-line 1) (narrow-to-region beginning (or (save-excursion (if (re-search-forward "^\\(\\.#\\|* \\)" nil t) (progn (while (and (or (equal (get-text-property (match-beginning 0) 'face) 'muse-verbatim) (get-text-property (match-beginning 0) 'read-only)) (re-search-forward "^\\(\\.#\\|* \\)" nil t))) (if (not (or (equal (get-text-property (match-beginning 0) 'face) 'muse-verbatim) (get-text-property (match-beginning 0) 'read-only))) (match-beginning 0))))) (point-max))) t)))) ;; Avoid marking up sections inside tags. (setq planner-publish-prepare-regexps nil)