lisp/my-old-planner.el
changeset 1 a234a7579958
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/my-old-planner.el	Thu Oct 13 08:46:42 2011 -0400
@@ -0,0 +1,448 @@
+; timeclock functionnality
+(require 'timeclock)
+(require 'calendar)
+
+;(timeclock-modeline-display)
+(add-hook 'kill-emacs-hook 'timeclock-query-out)
+ 
+;; planner mode
+
+;;; helper functions
+
+(defun planner-date-to-calendar (date)
+  "Convert a planner date string like \"YYYY.MM.DD\" in
+   a calendar date list (month day year)."
+  (let ((split-date 
+         (mapcar 'string-to-number 
+                 (split-string (file-name-nondirectory date) "\\."))))
+    (list (nth 1 split-date) 
+          (nth 2 split-date)
+          (nth 0 split-date))))
+
+(defsubst calendar-date-to-planner (date)
+; "Convert a planner date string like \"YYYY.MM.DD\" in
+; a calendar date list (month day year)."
+  (format "%04d.%02d.%02d"
+          (nth 2 date)
+          (nth 0 date)
+          (nth 1 date)))
+
+;; Moving functions
+
+(defun planner-goto-schedule ()
+  "Create if necessary and go to a Schedule section in current planner.
+Ensure to preserved the buffer-modified-p value so that a kill will not
+preserve the automated change for nothing."
+  (interactive)
+  (goto-char (point-min))
+  (let ((modified (buffer-modified-p)))
+    (unless (re-search-forward "^\\* Schedule\n\n" nil t)
+      (re-search-forward "^\\* Notes")
+      (beginning-of-line)
+      (insert "* Schedule\n\n\n\n")
+      (forward-line -2)
+      (set-buffer-modified-p modified))))
+
+(defun planner-goto-events ()
+  "Create if necessary and go to a Events section just before the
+Schedule one (which is also create if necessary) in current planner.
+Ensure to preserved the buffer-modified-p value so that a kill will
+not preserve the automated change for nothing."
+  (interactive)
+  (goto-char (point-min))
+  (let ((modified (buffer-modified-p)))
+    (unless (re-search-forward "^\\* Events\n\n" nil t)
+      (planner-goto-schedule)
+      (re-search-backward "^\\* Schedule")
+      (insert "* Events\n\n\n\n")
+      (forward-line -2)
+      (set-buffer-modified-p modified))))
+
+;; The reminders converter.
+;;
+;; It now used a custom diary file, so you
+;; can included it without breaking your normal diary-file.
+;; The function can also set some arguments.  I used it to call
+;; a modified remconv which accept a "euro" argument to output
+;; euro style date.
+;;
+;; I no more used those ones now since I'm directly marking the
+;; calendar and the add-appt from the planner files.
+
+(defcustom planner-diary-file diary-file
+  "Diary file where to add planner entries.  You can set this value to
+another file than `diary-file' and add in `diary-file' the #include
+\"~/your-planner-diary-file-name\".  Just make sure that you call
+`planner-convert-reminders' before `include-other-diary-files' in the
+`list-diary-entries-hook'."
+  :type 'file
+  :group 'planner)
+
+(defcustom planner-remconv-command "remconv"
+  "A command that output diary compatible entries.  Can take arguments
+also."
+  :type 'string
+  :group 'planner)
+
+(defun planner-convert-reminders ()
+  "Generate a diary file from a .reminders file.
+You can add this this function to the `list-diary-entries-hook'"
+  (with-current-buffer (find-file-noselect planner-diary-file)
+    (erase-buffer)
+    (insert (shell-command-to-string planner-remconv-command))
+    (save-buffer)))
+
+;; redefinition of planner-maybe-remove-file 
+;;
+;; The kill-buffer of this function make me crazy.  I modify it so you
+;; can change it's behavior using this custom variable.  Personnaly, I
+;; used ignore and let emacs ask me to save it when I kill it.  Take
+;; also note that I change the regexp to remove any file that only
+;; have empty section (section with just a first level heading).  This
+;; is to cope with the new Events and Schedule sections.
+
+(defcustom planner-not-empty-file 'kill-this-buffer
+  "Command to run when a planner file is not empty.  Can be
+kill-this-buffer, save-buffer or ignore for example."
+  :type 'function
+  :group 'planner)
+
+(defvar local-planner-empty-line-regexp "\\(\\* .*\\|[[:space:]]*\\)")
+(defvar planner-empty-file-regexp 
+  (concat "\\(^" local-planner-empty-line-regexp
+          "\n\\)*[[:space:]]*\\'"))
+
+; redefinition
+
+(defun planner-maybe-remove-file ()
+  "This function remove the file if it contains only first level
+headings and empty lines."
+  (interactive)
+  (goto-char (point-min))
+  (if (looking-at planner-empty-file-regexp)
+      (let ((filename buffer-file-name))
+	(set-buffer-modified-p nil)
+	(kill-buffer (current-buffer))
+	(delete-file filename))
+    (funcall planner-not-empty-file)))
+
+;; planner-browse-url bbdb url handling
+;;
+;; My emacs-wiki doesn't like bbdb url like [[bbdb://Fabien Niņoles]]
+;; I make this advice so he can replace _ with space and __ with _ in the
+;; url before letting planner-browse-url handling it.
+
+(defadvice planner-browse-url (before local-bbdb-url-pretreatment (url))
+  "Pretreatment of some URL."
+  (if (string-match "^bbdb://\\(.+\\)" url)
+      (setq url
+            (mapconcat 
+             '(lambda (str) (subst-char-in-string ?_ ?  str t))
+             (split-string url "__")
+             "_"))))
+
+(ad-activate 'planner-browse-url)
+
+;; diary support functions
+;;
+;; This functions add diary entries into the Schedule and Events
+;; sections of a planner buffer.  I hook it to the
+;; `planner-seek-to-first' function and only if the Events section
+;; doesn't already exist.  The best should be that it check if the
+;; entry doesn't exist already instead but currently it works pretty
+;; good.  You can always call it interactively if you want to update
+;; your Events/Schedule.  Just make sure that you don't add the
+;; `planner-convert-reminder' set in the `list-diary-entries-hook'
+;; setup.
+
+(defun planner-insert-diary ()
+  "Insert the diary schedule in the planner buffer."
+  (interactive)
+  (let*
+    ((entries (list-diary-entries (planner-date-to-calendar (emacs-wiki-page-name)) 1))
+     (events))
+    (planner-goto-schedule)
+    (while entries
+      (let* ((entry (nth 1 (car entries)))
+             (lines (split-string entry "\n"))
+             (line))
+        (while (setq line (car lines))
+          (if (string-match
+               "^[[:space:]]*\\([0-9]+:[0-9]\\{2\\}\\(?:am\\|pm\\)?\\)\\(-[0-9]+:[0-9]\\{2\\}\\(?:am\\|pm\\)?\\)?[[:space:]]+\\(.*\\)$" 
+               line)
+          (let ((starttime (match-string 1 line))
+                (endtime (match-string 2 line))
+                (description (match-string 3 line)))
+            (insert (concat "  " starttime
+                            " | " description
+                            (if endtime
+                                (let
+                                    ((minutes
+                                      (- (appt-convert-time (substring endtime 1 nil))
+                                         (appt-convert-time starttime))))
+                                  (format " (%d:%02d)"
+                                          (/ minutes 60)
+                                          (% minutes 60)))
+                                "")
+                              "\n")))
+          (setq events (cons line events)))
+          (setq lines (cdr lines))))
+      (setq entries (cdr entries)))
+    (planner-goto-events)
+    (while events
+      (insert (concat " - " (car events) "\n"))
+      (setq events (cdr events)))))
+
+(defadvice planner-seek-to-first (after planner-insert-diary-ad ())
+  "Insert the diary into a newly create buffer."
+  (if (string-match planner-date-regexp (emacs-wiki-page-name))
+      (save-excursion
+        (goto-char (point-min))
+        (if (not (re-search-forward "^\\* Events[[:space:]]*$" nil t))
+            (planner-insert-diary)))))
+
+(ad-activate 'planner-seek-to-first)
+
+;; appt support
+;;
+;; I'm loading now my appointments directly from the planner list.  It
+;; has the same functionnality as the regular appt-make-list,
+;; including its own planner-prev-appt-check variable.  It's set as an
+;; after-advice to appt-check.  I also add a function that parse the
+;; current line and add the appt to it's list.  The
+;; planner-appt-entry-regexp are setup to catch any 'HH:MM | Message
+;; (HH:MM)' line (just like allrems). You can set it up also to ignore
+;; some special line (like those beginning with '&' for example).
+
+(require 'appt)
+
+(defvar planner-appt-entry-regexp 
+  "^[[:space:]]*\\([0-9]+:[0-9]\\{2\\}\\)[[:space:]]*|[[:space:]]*\\([^&].*\\)[[:space:]]*\\(([0-9]+:[0-9]\\{2\\})\\)?[[:space:]]*$"
+  "Regexp that match a appt entry in planner.")
+
+(defun planner-appt-add ()
+  "Add this line as an appointment.  The line should match
+`planner-appt-entry-regexp'."
+  (interactive)
+  (save-excursion
+    (beginning-of-line)
+    (if (looking-at planner-appt-entry-regexp)
+        (appt-add (match-string-no-properties 1) (match-string-no-properties 2))
+      (error "No appointment on this line"))))
+
+(defun planner-make-appt-list ()
+  "Load today planner file and make appt-list from schedule."
+  (interactive)
+  (save-excursion
+    (save-window-excursion 
+      (planner-goto-today)
+      (while (re-search-forward planner-appt-entry-regexp nil t)
+        (appt-add (match-string-no-properties 1) (match-string-no-properties 2))))))
+
+(defvar planner-prev-appt-check nil
+  "Determine the last time appt-check are called")
+
+(defadvice appt-check (after planner-appt-check-ad ())
+  "Call planner-make-appt-list the first time and every day."
+  (let* ((now (decode-time))
+         (cur-hour (nth 2 now))
+         (cur-min (nth 1 now))
+         (cur-comp-time (+ (* cur-hour 60) cur-min)))
+    (if (or (null planner-prev-appt-check)
+            (< cur-comp-time planner-prev-appt-check))
+        (planner-make-appt-list))
+    (setq planner-prev-appt-check cur-comp-time)))
+
+(ad-activate 'appt-check)
+
+;; calendar support
+;;
+;; This function are used to mark calendar entries directly from the
+;; planner files.  I try to optimize it with a helper function that
+;; take the list of planner-date string corresponding to the three
+;; months display of the calendar (same behavior as the
+;; mark-diary-entries-hook).  One of the helper are made in elisp, and
+;; the other used an external perl script to parse the entries.  The
+;; only thing check is a non-empty (I mean non-space) Events section.
+;; Used it as a hook to the `mark-diary-entries-hook'.  Since this
+;; function also search for diary entries, you can used it in
+;; conjunction with diary.  And with the `planner-diary-insert'
+;; function, you just have to type 'n' at the calendar day to see both
+;; the diary and your normal planner entry.  The helper script is call events
+;; and it's very short (see documentation of planner-mark-calendar-external-helper
+;; for the script. The script also return the entry line so that you can remove
+;; specially mark entry if you care, either by directly modifying the
+;; script or by modifying the planner-mark-calendar-external-helper
+;; function.
+
+(defcustom planner-mark-calendar-helper 'planner-mark-calendar-internal-helper
+  "Helper function to determinate which day have a non-empty events.
+Currently, it exist two functions to do it:
+`planner-mark-calendar-internal-helper' which is a lisp
+implementation, and `planner-mark-calendar-external-helper' which used
+a external script to do most of the parsing."
+  :type 'function
+  :group 'planner)
+
+(defun planner-mark-calendar ()
+  "Look for planner file with non-empty events."
+  (save-window-excursion
+    (set-buffer calendar-buffer)
+    (let ((prev-month displayed-month)
+          (prev-year displayed-year)
+          (succ-month displayed-month)
+          (succ-year displayed-year)
+          (last-day)
+          (day)
+          (files nil))
+      (increment-calendar-month succ-month succ-year 1)
+      (increment-calendar-month prev-month prev-year -1)
+      (setq day (calendar-absolute-from-gregorian (list prev-month 1 prev-year)))
+      (setq last-day 
+            (calendar-absolute-from-gregorian 
+             (list succ-month 
+                   (calendar-last-day-of-month succ-month succ-year)
+                   succ-year))) 
+      (while (<= day last-day)
+        (let* ((date (calendar-gregorian-from-absolute day))
+               (file 
+                (expand-file-name 
+                 (calendar-date-to-planner date)
+                 planner-directory)))
+          (if (file-readable-p file)
+              (setq files (cons file files))))
+        (setq day (1+ day)))
+      (setq files (funcall planner-mark-calendar-helper files))
+      (while files
+        (mark-visible-calendar-date (planner-date-to-calendar (car files)))
+        (setq files (cdr files))))))
+
+(defun planner-mark-calendar-internal-helper (files)
+  "Local (elisp) helper function for `planner-mark-calendar'."
+  (let ((correct))
+    (while files
+      (find-file-other-window (car files))
+      (goto-char (point-min))
+      (if (re-search-forward "^\\* Events" nil t)
+          (progn
+            (forward-line 1)
+            (if (re-search-forward 
+                 "[^[:space:]]+"
+                 (save-excursion
+                   (re-search-forward "^\\* " nil t)
+                   (let ((p (match-beginning 0)))
+                     (and p (1- p))))
+                 t)
+                (setq correct (cons (car files) correct)))))
+      (if (not (buffer-modified-p)) (kill-this-buffer))
+      (setq files (cdr files)))
+    correct))
+
+(defcustom planner-mark-calendar-external-script "events %s"
+  "Command line that select files that must be marked.
+This string is send to the shell with %s replace with a list
+of space separate (maybe non-existing) file names. An example
+of such script follow:
+
+#!/usr/bin/perl -s
+
+for $file (sort @ARGV) {
+    open (FILE, $file) || die \"Cannot open file $file\\n\";
+    $inevents = 0;
+    while (<FILE>) {
+            if ($inevents == 0) {
+                if (/^\\* Events/) {
+                    $inevents = 1;
+                }
+            } elsif (/^\\* /) {
+                $inevents = 0;
+                break;
+            }
+            elsif (/^\\s*\\S/) {
+                printf \"$file: $_\";
+            }
+        }
+    close (FILE);
+};
+"
+  :type 'string
+  :group 'planner)
+  
+(defun planner-mark-calendar-external-helper (files)
+  "Use an external application to parse the planner files.
+The command line is set with the `planner-mark-calendar-external-script'
+variable and must take a list of files as arguments."
+  (let* ((command (concat "events " 
+                         (mapconcat 'identity files " ")))
+         (output (split-string (shell-command-to-string command) "\n"))
+         (results))
+    (while output
+      (if (string-match "^\\([^:]+\\):" (car output))
+          (setq results 
+                (cons (match-string 1 (car output))
+                      results)))
+      (setq output (cdr output)))
+    results))
+
+
+;; planner-mode key mapping
+;;
+;; No comment for this one.  Do ye want.
+
+(eval-after-load "planner"
+  '(progn
+     (define-key planner-mode-map [(control ?c) (control ?w)]
+       'planner-goto-schedule)
+     (define-key planner-mode-map [(control ?c) (control ?n)]
+       'planner-create-note)
+     (define-key planner-mode-map [(control ?c) (control ?e)]
+       'planner-appt-add)))
+
+;; global key mapping
+
+(define-key ctl-x-map "ta" 'planner-create-task)
+(define-key ctl-x-map "ts" 'planner-goto-today)
+(define-key ctl-x-map "ti" 'timeclock-in)
+(define-key ctl-x-map "to" 'timeclock-out)
+(define-key ctl-x-map "tc" 'timeclock-change)
+(define-key ctl-x-map "tr" 'timeclock-reread-log)
+(define-key ctl-x-map "tv" 'timeclock-status-string)
+;(define-key ctl-x-map "tu" 'timeclock-update-modeline)
+;(define-key ctl-x-map "tw" 'timeclock-when-to-leave-string)
+
+
+(defun local-planner-kill-buffer ()
+  "Kill the buffer and erase it if empty."
+  (interactive)
+  (let (
+        (planner-not-empty-file 'kill-this-buffer)
+;        (planner-not-empty-file (lambda () (save-buffer) (kill-this-buffer)))
+       )
+    (planner-maybe-remove-file)))
+
+;; planner-mode  key mapping
+(eval-after-load "planner"
+  '(progn
+     (define-key planner-mode-map [(control ?c) (control ?w)]
+       'planner-goto-schedule)
+     (define-key planner-mode-map [(control ?c) (control ?n)]
+       'planner-create-note)
+     (define-key planner-mode-map [(control ?c) (control ?k)]
+       'local-planner-kill-buffer)
+     (define-key planner-mode-map [(control ?c) (control ?e)]
+       'planner-appt-add)))
+
+;; global key mapping
+
+(define-key ctl-x-map "ta" 'planner-create-task)
+(define-key ctl-x-map "ts" 'planner-goto-today)
+(define-key ctl-x-map "tj" 'planner-goto)
+(define-key ctl-x-map "ti" 'timeclock-in)
+(define-key ctl-x-map "to" 'timeclock-out)
+(define-key ctl-x-map "tc" 'timeclock-change)
+(define-key ctl-x-map "tr" 'timeclock-reread-log)
+(define-key ctl-x-map "tv" 'timeclock-status-string)
+;(define-key ctl-x-map "tu" 'timeclock-update-modeline)
+;(define-key ctl-x-map "tw" 'timeclock-when-to-leave-string)
+
+(provide 'local-planner)