lisp/my-old-planner.el
changeset 1 a234a7579958
equal deleted inserted replaced
0:df7496e40bee 1:a234a7579958
       
     1 ; timeclock functionnality
       
     2 (require 'timeclock)
       
     3 (require 'calendar)
       
     4 
       
     5 ;(timeclock-modeline-display)
       
     6 (add-hook 'kill-emacs-hook 'timeclock-query-out)
       
     7  
       
     8 ;; planner mode
       
     9 
       
    10 ;;; helper functions
       
    11 
       
    12 (defun planner-date-to-calendar (date)
       
    13   "Convert a planner date string like \"YYYY.MM.DD\" in
       
    14    a calendar date list (month day year)."
       
    15   (let ((split-date 
       
    16          (mapcar 'string-to-number 
       
    17                  (split-string (file-name-nondirectory date) "\\."))))
       
    18     (list (nth 1 split-date) 
       
    19           (nth 2 split-date)
       
    20           (nth 0 split-date))))
       
    21 
       
    22 (defsubst calendar-date-to-planner (date)
       
    23 ; "Convert a planner date string like \"YYYY.MM.DD\" in
       
    24 ; a calendar date list (month day year)."
       
    25   (format "%04d.%02d.%02d"
       
    26           (nth 2 date)
       
    27           (nth 0 date)
       
    28           (nth 1 date)))
       
    29 
       
    30 ;; Moving functions
       
    31 
       
    32 (defun planner-goto-schedule ()
       
    33   "Create if necessary and go to a Schedule section in current planner.
       
    34 Ensure to preserved the buffer-modified-p value so that a kill will not
       
    35 preserve the automated change for nothing."
       
    36   (interactive)
       
    37   (goto-char (point-min))
       
    38   (let ((modified (buffer-modified-p)))
       
    39     (unless (re-search-forward "^\\* Schedule\n\n" nil t)
       
    40       (re-search-forward "^\\* Notes")
       
    41       (beginning-of-line)
       
    42       (insert "* Schedule\n\n\n\n")
       
    43       (forward-line -2)
       
    44       (set-buffer-modified-p modified))))
       
    45 
       
    46 (defun planner-goto-events ()
       
    47   "Create if necessary and go to a Events section just before the
       
    48 Schedule one (which is also create if necessary) in current planner.
       
    49 Ensure to preserved the buffer-modified-p value so that a kill will
       
    50 not preserve the automated change for nothing."
       
    51   (interactive)
       
    52   (goto-char (point-min))
       
    53   (let ((modified (buffer-modified-p)))
       
    54     (unless (re-search-forward "^\\* Events\n\n" nil t)
       
    55       (planner-goto-schedule)
       
    56       (re-search-backward "^\\* Schedule")
       
    57       (insert "* Events\n\n\n\n")
       
    58       (forward-line -2)
       
    59       (set-buffer-modified-p modified))))
       
    60 
       
    61 ;; The reminders converter.
       
    62 ;;
       
    63 ;; It now used a custom diary file, so you
       
    64 ;; can included it without breaking your normal diary-file.
       
    65 ;; The function can also set some arguments.  I used it to call
       
    66 ;; a modified remconv which accept a "euro" argument to output
       
    67 ;; euro style date.
       
    68 ;;
       
    69 ;; I no more used those ones now since I'm directly marking the
       
    70 ;; calendar and the add-appt from the planner files.
       
    71 
       
    72 (defcustom planner-diary-file diary-file
       
    73   "Diary file where to add planner entries.  You can set this value to
       
    74 another file than `diary-file' and add in `diary-file' the #include
       
    75 \"~/your-planner-diary-file-name\".  Just make sure that you call
       
    76 `planner-convert-reminders' before `include-other-diary-files' in the
       
    77 `list-diary-entries-hook'."
       
    78   :type 'file
       
    79   :group 'planner)
       
    80 
       
    81 (defcustom planner-remconv-command "remconv"
       
    82   "A command that output diary compatible entries.  Can take arguments
       
    83 also."
       
    84   :type 'string
       
    85   :group 'planner)
       
    86 
       
    87 (defun planner-convert-reminders ()
       
    88   "Generate a diary file from a .reminders file.
       
    89 You can add this this function to the `list-diary-entries-hook'"
       
    90   (with-current-buffer (find-file-noselect planner-diary-file)
       
    91     (erase-buffer)
       
    92     (insert (shell-command-to-string planner-remconv-command))
       
    93     (save-buffer)))
       
    94 
       
    95 ;; redefinition of planner-maybe-remove-file 
       
    96 ;;
       
    97 ;; The kill-buffer of this function make me crazy.  I modify it so you
       
    98 ;; can change it's behavior using this custom variable.  Personnaly, I
       
    99 ;; used ignore and let emacs ask me to save it when I kill it.  Take
       
   100 ;; also note that I change the regexp to remove any file that only
       
   101 ;; have empty section (section with just a first level heading).  This
       
   102 ;; is to cope with the new Events and Schedule sections.
       
   103 
       
   104 (defcustom planner-not-empty-file 'kill-this-buffer
       
   105   "Command to run when a planner file is not empty.  Can be
       
   106 kill-this-buffer, save-buffer or ignore for example."
       
   107   :type 'function
       
   108   :group 'planner)
       
   109 
       
   110 (defvar local-planner-empty-line-regexp "\\(\\* .*\\|[[:space:]]*\\)")
       
   111 (defvar planner-empty-file-regexp 
       
   112   (concat "\\(^" local-planner-empty-line-regexp
       
   113           "\n\\)*[[:space:]]*\\'"))
       
   114 
       
   115 ; redefinition
       
   116 
       
   117 (defun planner-maybe-remove-file ()
       
   118   "This function remove the file if it contains only first level
       
   119 headings and empty lines."
       
   120   (interactive)
       
   121   (goto-char (point-min))
       
   122   (if (looking-at planner-empty-file-regexp)
       
   123       (let ((filename buffer-file-name))
       
   124 	(set-buffer-modified-p nil)
       
   125 	(kill-buffer (current-buffer))
       
   126 	(delete-file filename))
       
   127     (funcall planner-not-empty-file)))
       
   128 
       
   129 ;; planner-browse-url bbdb url handling
       
   130 ;;
       
   131 ;; My emacs-wiki doesn't like bbdb url like [[bbdb://Fabien Niņoles]]
       
   132 ;; I make this advice so he can replace _ with space and __ with _ in the
       
   133 ;; url before letting planner-browse-url handling it.
       
   134 
       
   135 (defadvice planner-browse-url (before local-bbdb-url-pretreatment (url))
       
   136   "Pretreatment of some URL."
       
   137   (if (string-match "^bbdb://\\(.+\\)" url)
       
   138       (setq url
       
   139             (mapconcat 
       
   140              '(lambda (str) (subst-char-in-string ?_ ?  str t))
       
   141              (split-string url "__")
       
   142              "_"))))
       
   143 
       
   144 (ad-activate 'planner-browse-url)
       
   145 
       
   146 ;; diary support functions
       
   147 ;;
       
   148 ;; This functions add diary entries into the Schedule and Events
       
   149 ;; sections of a planner buffer.  I hook it to the
       
   150 ;; `planner-seek-to-first' function and only if the Events section
       
   151 ;; doesn't already exist.  The best should be that it check if the
       
   152 ;; entry doesn't exist already instead but currently it works pretty
       
   153 ;; good.  You can always call it interactively if you want to update
       
   154 ;; your Events/Schedule.  Just make sure that you don't add the
       
   155 ;; `planner-convert-reminder' set in the `list-diary-entries-hook'
       
   156 ;; setup.
       
   157 
       
   158 (defun planner-insert-diary ()
       
   159   "Insert the diary schedule in the planner buffer."
       
   160   (interactive)
       
   161   (let*
       
   162     ((entries (list-diary-entries (planner-date-to-calendar (emacs-wiki-page-name)) 1))
       
   163      (events))
       
   164     (planner-goto-schedule)
       
   165     (while entries
       
   166       (let* ((entry (nth 1 (car entries)))
       
   167              (lines (split-string entry "\n"))
       
   168              (line))
       
   169         (while (setq line (car lines))
       
   170           (if (string-match
       
   171                "^[[:space:]]*\\([0-9]+:[0-9]\\{2\\}\\(?:am\\|pm\\)?\\)\\(-[0-9]+:[0-9]\\{2\\}\\(?:am\\|pm\\)?\\)?[[:space:]]+\\(.*\\)$" 
       
   172                line)
       
   173           (let ((starttime (match-string 1 line))
       
   174                 (endtime (match-string 2 line))
       
   175                 (description (match-string 3 line)))
       
   176             (insert (concat "  " starttime
       
   177                             " | " description
       
   178                             (if endtime
       
   179                                 (let
       
   180                                     ((minutes
       
   181                                       (- (appt-convert-time (substring endtime 1 nil))
       
   182                                          (appt-convert-time starttime))))
       
   183                                   (format " (%d:%02d)"
       
   184                                           (/ minutes 60)
       
   185                                           (% minutes 60)))
       
   186                                 "")
       
   187                               "\n")))
       
   188           (setq events (cons line events)))
       
   189           (setq lines (cdr lines))))
       
   190       (setq entries (cdr entries)))
       
   191     (planner-goto-events)
       
   192     (while events
       
   193       (insert (concat " - " (car events) "\n"))
       
   194       (setq events (cdr events)))))
       
   195 
       
   196 (defadvice planner-seek-to-first (after planner-insert-diary-ad ())
       
   197   "Insert the diary into a newly create buffer."
       
   198   (if (string-match planner-date-regexp (emacs-wiki-page-name))
       
   199       (save-excursion
       
   200         (goto-char (point-min))
       
   201         (if (not (re-search-forward "^\\* Events[[:space:]]*$" nil t))
       
   202             (planner-insert-diary)))))
       
   203 
       
   204 (ad-activate 'planner-seek-to-first)
       
   205 
       
   206 ;; appt support
       
   207 ;;
       
   208 ;; I'm loading now my appointments directly from the planner list.  It
       
   209 ;; has the same functionnality as the regular appt-make-list,
       
   210 ;; including its own planner-prev-appt-check variable.  It's set as an
       
   211 ;; after-advice to appt-check.  I also add a function that parse the
       
   212 ;; current line and add the appt to it's list.  The
       
   213 ;; planner-appt-entry-regexp are setup to catch any 'HH:MM | Message
       
   214 ;; (HH:MM)' line (just like allrems). You can set it up also to ignore
       
   215 ;; some special line (like those beginning with '&' for example).
       
   216 
       
   217 (require 'appt)
       
   218 
       
   219 (defvar planner-appt-entry-regexp 
       
   220   "^[[:space:]]*\\([0-9]+:[0-9]\\{2\\}\\)[[:space:]]*|[[:space:]]*\\([^&].*\\)[[:space:]]*\\(([0-9]+:[0-9]\\{2\\})\\)?[[:space:]]*$"
       
   221   "Regexp that match a appt entry in planner.")
       
   222 
       
   223 (defun planner-appt-add ()
       
   224   "Add this line as an appointment.  The line should match
       
   225 `planner-appt-entry-regexp'."
       
   226   (interactive)
       
   227   (save-excursion
       
   228     (beginning-of-line)
       
   229     (if (looking-at planner-appt-entry-regexp)
       
   230         (appt-add (match-string-no-properties 1) (match-string-no-properties 2))
       
   231       (error "No appointment on this line"))))
       
   232 
       
   233 (defun planner-make-appt-list ()
       
   234   "Load today planner file and make appt-list from schedule."
       
   235   (interactive)
       
   236   (save-excursion
       
   237     (save-window-excursion 
       
   238       (planner-goto-today)
       
   239       (while (re-search-forward planner-appt-entry-regexp nil t)
       
   240         (appt-add (match-string-no-properties 1) (match-string-no-properties 2))))))
       
   241 
       
   242 (defvar planner-prev-appt-check nil
       
   243   "Determine the last time appt-check are called")
       
   244 
       
   245 (defadvice appt-check (after planner-appt-check-ad ())
       
   246   "Call planner-make-appt-list the first time and every day."
       
   247   (let* ((now (decode-time))
       
   248          (cur-hour (nth 2 now))
       
   249          (cur-min (nth 1 now))
       
   250          (cur-comp-time (+ (* cur-hour 60) cur-min)))
       
   251     (if (or (null planner-prev-appt-check)
       
   252             (< cur-comp-time planner-prev-appt-check))
       
   253         (planner-make-appt-list))
       
   254     (setq planner-prev-appt-check cur-comp-time)))
       
   255 
       
   256 (ad-activate 'appt-check)
       
   257 
       
   258 ;; calendar support
       
   259 ;;
       
   260 ;; This function are used to mark calendar entries directly from the
       
   261 ;; planner files.  I try to optimize it with a helper function that
       
   262 ;; take the list of planner-date string corresponding to the three
       
   263 ;; months display of the calendar (same behavior as the
       
   264 ;; mark-diary-entries-hook).  One of the helper are made in elisp, and
       
   265 ;; the other used an external perl script to parse the entries.  The
       
   266 ;; only thing check is a non-empty (I mean non-space) Events section.
       
   267 ;; Used it as a hook to the `mark-diary-entries-hook'.  Since this
       
   268 ;; function also search for diary entries, you can used it in
       
   269 ;; conjunction with diary.  And with the `planner-diary-insert'
       
   270 ;; function, you just have to type 'n' at the calendar day to see both
       
   271 ;; the diary and your normal planner entry.  The helper script is call events
       
   272 ;; and it's very short (see documentation of planner-mark-calendar-external-helper
       
   273 ;; for the script. The script also return the entry line so that you can remove
       
   274 ;; specially mark entry if you care, either by directly modifying the
       
   275 ;; script or by modifying the planner-mark-calendar-external-helper
       
   276 ;; function.
       
   277 
       
   278 (defcustom planner-mark-calendar-helper 'planner-mark-calendar-internal-helper
       
   279   "Helper function to determinate which day have a non-empty events.
       
   280 Currently, it exist two functions to do it:
       
   281 `planner-mark-calendar-internal-helper' which is a lisp
       
   282 implementation, and `planner-mark-calendar-external-helper' which used
       
   283 a external script to do most of the parsing."
       
   284   :type 'function
       
   285   :group 'planner)
       
   286 
       
   287 (defun planner-mark-calendar ()
       
   288   "Look for planner file with non-empty events."
       
   289   (save-window-excursion
       
   290     (set-buffer calendar-buffer)
       
   291     (let ((prev-month displayed-month)
       
   292           (prev-year displayed-year)
       
   293           (succ-month displayed-month)
       
   294           (succ-year displayed-year)
       
   295           (last-day)
       
   296           (day)
       
   297           (files nil))
       
   298       (increment-calendar-month succ-month succ-year 1)
       
   299       (increment-calendar-month prev-month prev-year -1)
       
   300       (setq day (calendar-absolute-from-gregorian (list prev-month 1 prev-year)))
       
   301       (setq last-day 
       
   302             (calendar-absolute-from-gregorian 
       
   303              (list succ-month 
       
   304                    (calendar-last-day-of-month succ-month succ-year)
       
   305                    succ-year))) 
       
   306       (while (<= day last-day)
       
   307         (let* ((date (calendar-gregorian-from-absolute day))
       
   308                (file 
       
   309                 (expand-file-name 
       
   310                  (calendar-date-to-planner date)
       
   311                  planner-directory)))
       
   312           (if (file-readable-p file)
       
   313               (setq files (cons file files))))
       
   314         (setq day (1+ day)))
       
   315       (setq files (funcall planner-mark-calendar-helper files))
       
   316       (while files
       
   317         (mark-visible-calendar-date (planner-date-to-calendar (car files)))
       
   318         (setq files (cdr files))))))
       
   319 
       
   320 (defun planner-mark-calendar-internal-helper (files)
       
   321   "Local (elisp) helper function for `planner-mark-calendar'."
       
   322   (let ((correct))
       
   323     (while files
       
   324       (find-file-other-window (car files))
       
   325       (goto-char (point-min))
       
   326       (if (re-search-forward "^\\* Events" nil t)
       
   327           (progn
       
   328             (forward-line 1)
       
   329             (if (re-search-forward 
       
   330                  "[^[:space:]]+"
       
   331                  (save-excursion
       
   332                    (re-search-forward "^\\* " nil t)
       
   333                    (let ((p (match-beginning 0)))
       
   334                      (and p (1- p))))
       
   335                  t)
       
   336                 (setq correct (cons (car files) correct)))))
       
   337       (if (not (buffer-modified-p)) (kill-this-buffer))
       
   338       (setq files (cdr files)))
       
   339     correct))
       
   340 
       
   341 (defcustom planner-mark-calendar-external-script "events %s"
       
   342   "Command line that select files that must be marked.
       
   343 This string is send to the shell with %s replace with a list
       
   344 of space separate (maybe non-existing) file names. An example
       
   345 of such script follow:
       
   346 
       
   347 #!/usr/bin/perl -s
       
   348 
       
   349 for $file (sort @ARGV) {
       
   350     open (FILE, $file) || die \"Cannot open file $file\\n\";
       
   351     $inevents = 0;
       
   352     while (<FILE>) {
       
   353             if ($inevents == 0) {
       
   354                 if (/^\\* Events/) {
       
   355                     $inevents = 1;
       
   356                 }
       
   357             } elsif (/^\\* /) {
       
   358                 $inevents = 0;
       
   359                 break;
       
   360             }
       
   361             elsif (/^\\s*\\S/) {
       
   362                 printf \"$file: $_\";
       
   363             }
       
   364         }
       
   365     close (FILE);
       
   366 };
       
   367 "
       
   368   :type 'string
       
   369   :group 'planner)
       
   370   
       
   371 (defun planner-mark-calendar-external-helper (files)
       
   372   "Use an external application to parse the planner files.
       
   373 The command line is set with the `planner-mark-calendar-external-script'
       
   374 variable and must take a list of files as arguments."
       
   375   (let* ((command (concat "events " 
       
   376                          (mapconcat 'identity files " ")))
       
   377          (output (split-string (shell-command-to-string command) "\n"))
       
   378          (results))
       
   379     (while output
       
   380       (if (string-match "^\\([^:]+\\):" (car output))
       
   381           (setq results 
       
   382                 (cons (match-string 1 (car output))
       
   383                       results)))
       
   384       (setq output (cdr output)))
       
   385     results))
       
   386 
       
   387 
       
   388 ;; planner-mode key mapping
       
   389 ;;
       
   390 ;; No comment for this one.  Do ye want.
       
   391 
       
   392 (eval-after-load "planner"
       
   393   '(progn
       
   394      (define-key planner-mode-map [(control ?c) (control ?w)]
       
   395        'planner-goto-schedule)
       
   396      (define-key planner-mode-map [(control ?c) (control ?n)]
       
   397        'planner-create-note)
       
   398      (define-key planner-mode-map [(control ?c) (control ?e)]
       
   399        'planner-appt-add)))
       
   400 
       
   401 ;; global key mapping
       
   402 
       
   403 (define-key ctl-x-map "ta" 'planner-create-task)
       
   404 (define-key ctl-x-map "ts" 'planner-goto-today)
       
   405 (define-key ctl-x-map "ti" 'timeclock-in)
       
   406 (define-key ctl-x-map "to" 'timeclock-out)
       
   407 (define-key ctl-x-map "tc" 'timeclock-change)
       
   408 (define-key ctl-x-map "tr" 'timeclock-reread-log)
       
   409 (define-key ctl-x-map "tv" 'timeclock-status-string)
       
   410 ;(define-key ctl-x-map "tu" 'timeclock-update-modeline)
       
   411 ;(define-key ctl-x-map "tw" 'timeclock-when-to-leave-string)
       
   412 
       
   413 
       
   414 (defun local-planner-kill-buffer ()
       
   415   "Kill the buffer and erase it if empty."
       
   416   (interactive)
       
   417   (let (
       
   418         (planner-not-empty-file 'kill-this-buffer)
       
   419 ;        (planner-not-empty-file (lambda () (save-buffer) (kill-this-buffer)))
       
   420        )
       
   421     (planner-maybe-remove-file)))
       
   422 
       
   423 ;; planner-mode  key mapping
       
   424 (eval-after-load "planner"
       
   425   '(progn
       
   426      (define-key planner-mode-map [(control ?c) (control ?w)]
       
   427        'planner-goto-schedule)
       
   428      (define-key planner-mode-map [(control ?c) (control ?n)]
       
   429        'planner-create-note)
       
   430      (define-key planner-mode-map [(control ?c) (control ?k)]
       
   431        'local-planner-kill-buffer)
       
   432      (define-key planner-mode-map [(control ?c) (control ?e)]
       
   433        'planner-appt-add)))
       
   434 
       
   435 ;; global key mapping
       
   436 
       
   437 (define-key ctl-x-map "ta" 'planner-create-task)
       
   438 (define-key ctl-x-map "ts" 'planner-goto-today)
       
   439 (define-key ctl-x-map "tj" 'planner-goto)
       
   440 (define-key ctl-x-map "ti" 'timeclock-in)
       
   441 (define-key ctl-x-map "to" 'timeclock-out)
       
   442 (define-key ctl-x-map "tc" 'timeclock-change)
       
   443 (define-key ctl-x-map "tr" 'timeclock-reread-log)
       
   444 (define-key ctl-x-map "tv" 'timeclock-status-string)
       
   445 ;(define-key ctl-x-map "tu" 'timeclock-update-modeline)
       
   446 ;(define-key ctl-x-map "tw" 'timeclock-when-to-leave-string)
       
   447 
       
   448 (provide 'local-planner)