浏览代码

Added org-id.el to EXPERIMENTAL.

Carsten Dominik 17 年之前
父节点
当前提交
af819de830
共有 1 个文件被更改,包括 285 次插入0 次删除
  1. 285 0
      EXPERIMENTAL/org-id.el

+ 285 - 0
EXPERIMENTAL/org-id.el

@@ -0,0 +1,285 @@
+;;; org-id.el --- Global identifier for Org-mode entries
+;; Copyright (C) 2008 Free Software Foundation, Inc.
+;;
+;; Author: Carsten Dominik <carsten at orgmode dot org>
+;; Keywords: outlines, hypermedia, calendar, wp
+;; Homepage: http://orgmode.org
+;; Version: 5.22a+
+;;
+;; This file is part of GNU Emacs.
+;;
+;; GNU Emacs is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 3, or (at your option)
+;; any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING.  If not, write to the
+;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+;; Boston, MA 02110-1301, USA.
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;
+;;; 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.
+
+;; It provides the following API:
+
+;; 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.
+;;
+;; org-id-get-from-refile-location
+;;        Use the refile interface to select an entry and get its ID.
+;;        If necessary, create an id for this item.
+;;
+;; TODO:
+;; get/create id at current entry, safe in kill or so.
+
+(require 'org)
+
+(defgroup org-id nil
+  "Options concerning global entry identifiers in Org-mode."
+  :tag "Org ID"
+  :group 'org)
+
+(defcustom org-id-tracking-file "~/.org-id"
+  "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)
+
+(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."
+  :group 'org-id
+  :type 'integer)
+
+(defun org-id-random-string (n)
+  "Return a string of N random characters."
+  (let ((rtn "") x)
+    (while (>= (setq n (1- n)) 0)
+      (setq x (random 62))
+      (setq rtn (concat rtn (cond
+			     ((< x 10) (char-to-string (+ ?0 x)))
+			     ((< x 36) (char-to-string (+ ?A x -10)))
+			     ((< x 62) (char-to-string (+ ?a x -36)))
+			     (t (error "xxx"))))))
+    rtn))
+
+(defvar org-id-values nil
+  "Association list of id keywords with largest index used.")
+
+(defun org-id-save ()
+  "Save `org-id-values' in `org-id-tracking-file'."
+  (with-temp-file org-id-tracking-file
+    (print org-id-values (current-buffer))))
+
+(defun org-id-load ()
+  "Read the data from `org-id-tracking-file'."
+  (setq org-id-values nil)
+  (with-temp-buffer
+    (condition-case nil
+        (progn
+          (insert-file-contents-literally org-id-tracking-file)
+          (goto-char (point-min))
+          (setq org-id-values (read (current-buffer))))
+      (error
+       (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)
+
+
+(defun org-id-update-id-locations ()
+  "FIXME"
+  (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)
+    (while (setq file (pop files))
+      (setq ids nil)
+      (with-current-buffer (org-get-agenda-file-buffer file)
+	(save-excursion
+	  (save-restriction
+	    (widen)
+	    (goto-char (point-min))
+	    (while (re-search-forward "^[ \t]*:ID:[ \t]+\\(\\S-+\\)[ \t]*$"
+				      nil t)
+	      (push (match-string 1) ids))
+	    (push (cons file ids) reg)))))
+    (org-release-buffers org-agenda-new-buffers)
+    (setq org-agenda-new-buffers nil)
+    (setq org-id-locations reg)
+    (org-id-locations-save)))
+
+(defun org-id-locations-save ()
+  "Save `org-id-locations' in `org-id-locations-file'."
+  (with-temp-file org-id-locations-file
+    (print org-id-locations (current-buffer))))
+
+(defun org-id-locations-load ()
+  "Read the data from `org-id-locations-file'."
+  (setq org-id-locations nil)
+  (with-temp-buffer
+    (condition-case nil
+        (progn
+          (insert-file-contents-literally org-id-locations-file)
+          (goto-char (point-min))
+          (setq org-id-locations (read (current-buffer))))
+      (error
+       (message "Could not read org-id-values from %s. Setting it to nil."
+                org-id-locations-file)))))
+
+(defun org-id-add-location (id file)
+  "Add the ID with location FILE to the database of id loations."
+  (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))
+	  (throw 'exit t)))
+      (push (list file id) org-id-locations))
+    (org-id-locations-save)))
+
+(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)))
+
+(defun org-id-find-id-in-file (id file)
+  "Return a marker pointing to the entry ID in FILE.
+If that files does not exist, or if it does not contain this ID,
+return nil."
+  (let (org-agenda-new-buffers m)
+    (cond
+     ((not file) nil)
+     ((not file-exists-p file) nil)
+     (t (with-current-buffer (org-get-agenda-file-buffer file)
+	  (setq pos (org-find-entry-with-id id))
+	  (when pos
+	    (cons file pos)))))))  
+
+(defun org-id-find (id)
+  "Return the location of the entry with the id ID.
+The return value is a cons cell with file name and location."
+  (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)))
+    (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))))
+    where))
+    
+
+(provide 'org-id)
+
+;;; org-id.el ends here
+