diff --git a/CONTRIB/lisp/org-id.el b/CONTRIB/lisp/org-id.el index 715557019..acc9c50f1 100644 --- a/CONTRIB/lisp/org-id.el +++ b/CONTRIB/lisp/org-id.el @@ -27,45 +27,236 @@ ;;; Commentary: ;; This file implements globally unique identifiers for Org-mode entries. -;; Identifiers are tored in the entry as an :ID: property. This file -;; provides functions to create and retrieve such identifies. +;; Identifiers are stored in the entry as an :ID: property. This file +;; provides functions to create and retrieve such identifiers. ;; It provides the following API: - +;; +;; +;; Higer-level-functions +;; +;; org-id-create +;; Create an ID for the entry at point if it does not yet have one. +;; Returns the ID (old or new). This function can be used +;; interactively, with prefix argument the creation of a new ID is +;; forced, even if there was an old one. +;; ;; org-id-get ;; Get the ID property of an entry. Using appropriate arguments -;; to the function, it can also create the id for this entry. +;; to the function, it can also create the ID for this entry. +;; +;; org-id-goto +;; Command to go to a specific ID, this command can be used +;; interactively. +;; +;; org-id-get-with-outline-path-completion +;; Retrieve the ID of an entry, using outline path completion. +;; This function can work for multiple files. +;; +;; org-id-get-with-outline-drilling +;; Retrieve the ID of an entry, using outline path completion. +;; This function only works for the current file. +;; +;; org-id-find +;; Find the location of an entry with specific id. ;; -;; FIXME: more to describe ;; TODO: -;; get/create id at current entry, safe in kill or so. +;; - get/create id at current entry, safe in kill or so. (require 'org) + +;;; Customization + (defgroup org-id nil "Options concerning global entry identifiers in Org-mode." :tag "Org ID" :group 'org) +(defcustom org-id-structure (list "Org" (user-login-name) t 4) + "Components of globaly unique id's created by Org-mode. +An Org-mode ID has 4 components: + +prefix A prefix to identify the ID type (default \"Org\". +creator The creator of the ID, defaults to the users login name. +incremental An incremental number, specific for the creator. +random A random sequence of characters, to make sure ID created + in distributed development will still be unique. + This may be a short random sequence, or an md5 sum created + based on the current time, the current computer, and the user." + :group 'org-id + :type '(list + (string :tag "Prefix") + (string :tag "Creator") + (boolean :tag "Incremental") + (choice :tag "Random part" + (const :tag "No random part" nil) + (integer :tag "N characters") + (const :tag "MD5 digest" md5)))) + (defcustom org-id-tracking-file "~/.org-id" + "The file for remembering the last ID number generated, for each type." + :group 'org-id + :type 'file) + +(defvar org-id-values nil + "Association list of ID types+creator with largest index used so far.") + +(defcustom org-id-locations-file "~/.org-id-locations" "The file for remembering the last ID number generated." :group 'org-id :type 'file) -(defcustom org-id-prefix (user-login-name) - "The string prefix of global id's created by a user. -When working with other people, make sure everyone has their own -ID prefix, in order to guarantee that id's created by differnt people -will always be distinct." - :group 'org-id - :type 'string) +(defvar org-id-locations nil + "List of files with ID's in those files.") -(defcustom org-id-random-length 4 - "Non-nil means, insert a random part into id's. -This will be a random alpha-numeric string with as many characters -as given by this option." +(defcustom org-id-extra-files 'org-agenda-multi-occur-extra-files + "Files to be searched for ID's, besides the agenda files." :group 'org-id - :type 'integer) + :type + '(choice + (symbol :tag "Variable") + (repeat :tag "List of files" + (file)))) + + +;;; The API functions + +(defun org-id-create (&optional force) + "Create an ID for the current entry and return it. +If the entry already has an ID, just return it. +With optional argument FORCE, force the creation of a new ID." + (interactive "P") + (when force + (org-entry-put (point) "ID" nil)) + (org-id-get (point) 'create)) + +(defun org-id-copy () + "Copy the ID of the entry at point to the kill ring. +Create an ID if necessary." + (interactive) + (kill-new (org-id-get nil 'create))) + +(defun org-id-get (&optional pom create type nrandom) + "Get the ID property of the entry at point-or-marker POM. +If POM is nil, refer to the entry at point. +If the entry does not have an ID, the function returns nil. +However, when CREATE is non nil, create an ID if none is present already. +TYPE and NRANDOM will be passed through to `org-id-new'. +In any case, the ID of the entry is returned." + (let ((id (org-entry-get pom "ID"))) + (cond + ((and id (stringp id) (string-match "\\S-" id)) + id) + (create + (setq id (org-id-new type nrandom)) + (org-entry-put pom "ID" id) + (org-id-add-location id (buffer-file-name (buffer-base-buffer))) + id) + (t nil)))) + +(defun org-id-get-with-outline-path-completion (&optional targets) + "Use outline-path-completion to retrieve the ID of an entry. +TARGETS may be a setting for `org-refile-targets' to define the elegible +headlines. When omitted, all headlines in all agenda files are +elegible. +It returns the ID of the entry. If necessary, the ID is created." + (let* ((org-refile-targets (or targets '((nil . (:maxlevel . 10))))) + (org-refile-use-outline-path + (if (caar org-refile-targets) 'file t)) + (spos (org-refile-get-location "Entry: ")) + (pom (and spos (move-marker (make-marker) (nth 3 spos) + (get-file-buffer (nth 1 spos)))))) + (org-id-get pom 'create) + (move-marker pom nil))) + +(defun org-id-get-with-outline-drilling (&optional targets) + "Use an outline-cycling interface to retrieve the ID of an entry. +This only finds entries in the current buffer, using `org-get-location'. +It returns the ID of the entry. If necessary, the ID is created." + (let* ((spos (org-get-location (current-buffer) org-goto-help)) + (pom (and spos (move-marker (make-marker) (car spos))))) + (org-id-get pom 'create) + (move-marker pom nil))) + +(defun org-id-goto (id) + "Switch to the buffer containing the entry with id ID. +Move the cursor to that entry in that buffer." + (interactive) + (let ((m (org-id-find id 'marker))) + (unless m + (error "Cannot find entry with ID \"%s\"" id)) + (switch-to-buffer (marker-buffer m)) + (goto-char m) + (org-show-context))) + +(defun org-id-find (id &optional markerp) + "Return the location of the entry with the id ID. +The return value is a cons cell (file-name . position), or nil +if there is no entry with that ID. +With optional argument MARKERP, return the position as a markerp." + (let ((file (org-id-find-id-file id)) + org-agenda-new-buffers where) + (when file + (setq where (org-id-find-id-in-file id file markerp))) + (unless where + (org-id-update-id-locations) + (setq file (org-id-find-id-file id)) + (when file + (setq where (org-id-find-id-in-file id file markerp)))) + where)) + +;;; Internal functions + +;; Creating new ids + +(defun org-id-new (&optional type creator n xrandom) + "Create a new globally unique ID. +The ID is a string with four colon-separated parts: + +1. The type or prefix, given by the argument TYPE, or by the first element + +2. The creator, given by the argument CREATOR, or by the second element + of `org-id-structure' (default is the user's login name). +3. An incremental number linked to the ID type. This is a number that + runs for each ID type from 1 up, each time a new ID is created. + Org-mode keeps track of these numbers in the file `org-id-tracking-file', + so if you only work on a single computer or synchronize this file, + this is enough as a unique identifier. If you work with other people, + or on different computers, the uniqueness of this number is not certain. + A specific value for N can be forces by passing it into the function. +4. An extra string guaranteeing the uniqueness of the ID. + This is either a random string of XRANDOM characters if XRANDOM is an + integer. If XRANDOM is the symbol `md5', the extra string is a MD5 digest + of a string consisting of ID info, the current time, and a random number. + If you are sure the sequence number (component 3) is unique in your + setting, the random part can be omitted from the ID. + +So a typical ID could look like \"Org:dominik:105:2HtZ\"." + (unless n (org-id-load)) + (let* ((type (or type (car org-id-structure))) + (creator (or creator (nth 1 org-id-structure))) + (n-p n) + (key (concat type ":" creator)) + (ass (and (not n-p) (assoc key org-id-values))) + (n (or n (1+ (or (cdr ass) 0)))) + (xrandom (or xrandom (nth 3 org-id-structure))) + (random + (cond + ((not xrandom) nil) + ((eq xrandom 'none) nil) + ((integerp xrandom) (org-id-random-string xrandom)) + ((eq xrandom 'md5) (org-id-md5 type creator n)) + (t nil)))) + (unless n-p + (if ass + (setcdr ass n) + (push (cons key n) org-id-values)) + (org-id-save)) + (concat type + ":" creator + ":" (number-to-string n) + (if random (concat ":" random))))) (defun org-id-random-string (n) "Return a string of N random characters." @@ -79,8 +270,16 @@ as given by this option." (t (error "xxx")))))) rtn)) -(defvar org-id-values nil - "Association list of id keywords with largest index used.") +(defun org-id-md5 (type creator n) + "Return the md5 digest of a string. +This function concatenates ID info with random stuff like the time +and then computes the md5 digest. The result should be unique." + (md5 (concat type ":" creator ":" (number-to-string (or n 0)) ":" + (system-name) ":" + (prin1-to-string (current-time)) ":" + (number-to-string (random))))) + +;; Storing id indices (defun org-id-save () "Save `org-id-values' in `org-id-tracking-file'." @@ -100,101 +299,18 @@ as given by this option." (message "Could not read org-id-values from %s. Setting it to nil." org-id-tracking-file))))) -(defun org-id-new (&optional type nrandom) - "Create a new globally unique id. -The id is a string with two or three colon-separated parts: - -1. The type or prefix, given by the argument TYPE, or the value - of `org-id-prefix' (which defaults to the user name). -2. A hopefully unique number. This is a number that runs for each ID type - from 1 up, each time a new ID is created. Org-mode keeps track - of these numbers in the file `org-id-tracking-file', so if you - only work on a single computer or synchronize this file, this is enough - as a unique identifier. If you work with other people, or on different - computers, the uniqueness of this number is not certain. In this case - you should use a value larger than 0 for NRADNOM (which defaults - to `org-id-random-length'). -3. A random string with NRANDOM or `org-id-random-length' characters. - If that length is 0, the random part will be omitted from the ID. - -So a typical ID could look like \"dominik:105:2HtZ\"." - (org-id-load) - (let* ((type (or type org-id-prefix)) - (ass (assoc type org-id-values)) - (n (1+ (or (cdr ass) 0))) - (nrandom (or nrandom org-id-random-length)) - (random (org-id-random-string nrandom))) - (if ass - (setcdr ass n) - (push (cons type n) org-id-values)) - (org-id-save) - (concat type ":" - (number-to-string n) - (if (> nrandom 0) (concat ":" random))))) - -(defun org-id-get (&optional pom create type nrandom) - "Get the ID property of the entry at point-or-marker POM. -If POM is nil, refer to the entry at point. -If the entry does not have an ID, the function returns nil. -However, when CREATE is non nil, create an ID if none is present already. -TYPE and NRANDOM will be passed through to `org-id-new'. -In any case, the ID of the entry is returned." - (or (org-entry-get pom "ID") - (and create - (let ((id (org-id-new type nrandom))) - (org-entry-put pom "ID" id) - id)))) - -(defun org-id-get-with-outline-path-completion (&optional targets) - "Use outline-path-completion to retrieve the id of an entry. -TARGETS may be a setting for `org-refile-targets' to define the elegible -headlines. When omitted, all headlines in all agenda files are -elegible. -It returns the id of the entry. If necessary, the id is created." - (let* ((org-refile-targets (or targets '((nil . (:maxlevel . 10))))) - (org-refile-use-outline-path - (if (caar org-refile-targets) 'file t)) - (spos (org-refile-get-location "Entry: ")) - (pom (and spos (move-marker (make-marker) (nth 3 spos) - (get-file-buffer (nth 1 spos)))))) - (org-id-get pom 'create) - (move-marker pom nil))) - -(defun org-id-get-with-outline-drilling (&optional targets) - "Use an outline-cycling interface to retrieve the id of an entry. -This only finds entries in the current buffer, using `org-get-location'. -It returns the id of the entry. If necessary, the id is created." - (let* ((spos (org-get-location (current-buffer) org-goto-help)) - (pom (and spos (move-marker (make-marker) (car spos))))) - (org-id-get pom 'create) - (move-marker pom nil))) - -(defvar org-id-locations nil - "Association list of id's with files.") - -(defcustom org-id-extra-files 'org-agenda-multi-occur-extra-files - "Files to be searched for ID's, besides the agenda files." - :group 'org-id - :type - '(choice - (symbol :tag "Variable") - (repeat :tag "List of files" - (file)))) - -(defcustom org-id-locations-file "~/.org-id-locations" - "The file for remembering the last ID number generated." - :group 'org-id - :type 'file) - +;; Storing ID locations (files) (defun org-id-update-id-locations () - "FIXME" + "Scan relevant files for ID's. +Store the relation between files and corresponding ID's." + (interactive) (let ((files (append (org-agenda-files) (if (symbolp org-id-extra-files) (symbol-value org-id-extra-files) org-id-extra-files))) org-agenda-new-buffers - file ids reg) + file ids reg found id) (while (setq file (pop files)) (setq ids nil) (with-current-buffer (org-get-agenda-file-buffer file) @@ -204,7 +320,11 @@ It returns the id of the entry. If necessary, the id is created." (goto-char (point-min)) (while (re-search-forward "^[ \t]*:ID:[ \t]+\\(\\S-+\\)[ \t]*$" nil t) - (push (match-string 1) ids)) + (setq id (org-match-string-no-properties 1)) + (if (member id found) + (error "Duplicate ID \"%s\"" id)) + (push id found) + (push id ids)) (push (cons file ids) reg))))) (org-release-buffers org-agenda-new-buffers) (setq org-agenda-new-buffers nil) @@ -230,23 +350,27 @@ It returns the id of the entry. If necessary, the id is created." org-id-locations-file))))) (defun org-id-add-location (id file) - "Add the ID with location FILE to the database of id loations." + "Add the ID with location FILE to the database of ID loations." + (unless org-id-locations (org-id-locations-load)) (catch 'exit (let ((locs org-id-locations) list) (while (setq list (pop locs)) (when (equal (file-truename file) (file-truename (car list))) - (setcdr list (cons id list)) + (setcdr list (cons id (cdr list))) (throw 'exit t))) (push (list file id) org-id-locations)) (org-id-locations-save))) +;; Finding entries with specified id + (defun org-id-find-id-file (id) "Query the id database for the file in which this ID is located." (unless org-id-locations (org-id-locations-load)) (catch 'found (mapc (lambda (x) (if (member id (cdr x)) (throw 'found (car x)))) - org-id-locations))) + org-id-locations) + nil)) (defun org-id-find-id-in-file (id file &optional markerp) "Return the position of the entry ID in FILE. @@ -254,36 +378,17 @@ If that files does not exist, or if it does not contain this ID, return nil. The position is returned as a cons cell (file-name . position). With optional argument MARKERP, return the position as a marker." - (let (org-agenda-new-buffers m buf) + (let (org-agenda-new-buffers m buf pos) (cond ((not file) nil) ((not (file-exists-p file)) nil) (t (with-current-buffer (setq buf (org-get-agenda-file-buffer file)) (setq pos (org-find-entry-with-id id)) (when pos - (cons file pos (move-marker (make-marker) pos buf)))))))) + (if markerp + (move-marker (make-marker) pos buf) + (cons file pos)))))))) -(defun org-id-find (id &optional markerp) - "Return the location of the entry with the id ID. -The return value is a cons cell (file-name . position), or nil -if there is no entry with that ID. -With optional argument MARKERP, return the position as a markerp." - (let ((file (org-id-find-id-file id)) - org-agenda-new-buffers where) - (when file - (setq where (org-id-find-id-in-file id file markerp))) - (unless where - (org-id-update-id-locations) - (setq file (org-id-find-id-file id)) - (when file - (setq where (org-id-find-id-in-file id file markerp)))) - where)) - -(defun org-id-goto (id) - "Switch to the buffer containing the entry with id ID. -Move the cursor to that entry in that buffer." - (interactive)) - (provide 'org-id) diff --git a/ChangeLog b/ChangeLog index 8fe8437aa..875c087b7 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,13 @@ +2008-02-27 Carsten Dominik + + * org-irc.el: Modified the installation instructions. + + * org.el (org-store-link): Removed the (require 'org-irc), this is + now handled by `org-default-extensions'. + (org-load-hook): This hok is now exlicitly defined and made + customizable. + (org-default-extensions): New option. + 2008-02-26 Carsten Dominik * org.el (org-agenda-to-appt): Set `org-deadline-warning-days' to diff --git a/Makefile b/Makefile index 0a80054d3..8956fa30a 100644 --- a/Makefile +++ b/Makefile @@ -228,7 +228,7 @@ relup: clean: rm -f $(ELCFILES) org.pdf org org.html orgcard.pdf orgcard.ps - rm -f *~ + rm -f *~ */*~ */*/*~ rm -f *.aux *.cp *.cps *.dvi *.fn *.fns *.ky *.kys *.pg *.pgs rm -f *.toc *.tp *.tps *.vr *.vrs *.log *.html *.ps rm -f orgcard_letter.tex orgcard_letter.pdf diff --git a/org-irc.el b/org-irc.el index 3a4bf2725..ac442fee7 100644 --- a/org-irc.el +++ b/org-irc.el @@ -28,10 +28,8 @@ ;; Link to an IRC session. Only ERC has been implemented at the ;; moment. ;; -;; Place org-irc.el in your load path and add this to your -;; .emacs: -;; -;; (require 'org-irc) +;; Please configure the variable `org-default-extensions' to ensure that +;; this extension will be loaded. ;; ;; Please note that at the moment only ERC is supported. Other clients ;; shouldn't be diffficult to add though. @@ -51,6 +49,8 @@ ;; ;;; Code: +(require 'org-irc) + (defvar org-irc-client 'erc "The IRC client to act on") (defvar org-irc-link-to-logs nil diff --git a/org.el b/org.el index 6b6ae577f..6668e824c 100644 --- a/org.el +++ b/org.el @@ -151,6 +151,32 @@ With prefix arg HERE, insert it at point." :group 'hypermedia :group 'calendar) +(defcustom org-load-hook '(org-load-default-extensions) + "Hook that is run after org.el has been loaded. +This happens also after `org' has been provided, so +requiring something in this hook that does a (require 'org) is ok." + :group 'org + :type 'hook) + +(defcustom org-default-extensions '(org-irc) + "Extensions that should always be loaded together with org.el +If the description starts with , this means the extension +will be autoloaded when needed, to preloading is not necessary." + :group 'org + :type + '(set :greedy t + (const :tag " Mouse support (org-mouse.el)" org-mouse) + (const :tag " Publishing (org-publish.el)" org-publish) + (const :tag " LaTeX export (org-export-latex.el)" org-export-latex) + (const :tag " IRC/ERC links (org-irc.el)" org-irc) + (const :tag " Apple Mail message links under OS X (org-mac-message.el)" org-mac-message) +)) + +(defun org-load-default-extensions () + "Load all extensions that are listed in `org-default-extensions'." + (mapc 'require org-default-extensions)) + + ;; FIXME: Needs a separate group... (defcustom org-completion-fallback-command 'hippie-expand "The expansion command called by \\[org-complete] in normal context. @@ -12006,7 +12032,6 @@ For links to usenet articles, arg negates `org-usenet-links-prefer-google'. For file links, arg negates `org-context-in-file-links'." (interactive "P") (setq org-store-link-plist nil) ; reset - (require 'org-irc) (let (link cpltxt desc description search txt) (cond