mirror of
https://git.savannah.gnu.org/git/emacs/org-mode.git
synced 2024-09-29 18:36:26 +00:00
Added org-id.el to EXPERIMENTAL.
This commit is contained in:
parent
b1bd77a3af
commit
af819de830
285
EXPERIMENTAL/org-id.el
Normal file
285
EXPERIMENTAL/org-id.el
Normal file
|
@ -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
|
||||
|
Loading…
Reference in a new issue