| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151 | 
							- ;;; org-contacts.el --- Contacts management
 
- ;; Copyright (C) 2010-2014 Julien Danjou <julien@danjou.info>
 
- ;; Author: Julien Danjou <julien@danjou.info>
 
- ;; Keywords: outlines, hypermedia, calendar
 
- ;;
 
- ;; This file is NOT part of GNU Emacs.
 
- ;;
 
- ;; This program 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 of the License, or
 
- ;; (at your option) any later version.
 
- ;; This program 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.  If not, see <http://www.gnu.org/licenses/>.
 
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
- ;;
 
- ;;; Commentary:
 
- ;; This file contains the code for managing your contacts into Org-mode.
 
- ;; To enter new contacts, you can use `org-capture' and a minimal template just like
 
- ;; this:
 
- ;;         ("c" "Contacts" entry (file "~/Org/contacts.org")
 
- ;;          "* %(org-contacts-template-name)
 
- ;; :PROPERTIES:
 
- ;; :EMAIL: %(org-contacts-template-email)
 
- ;; :END:")))
 
- ;;
 
- ;; You can also use a complex template, for example:
 
- ;;
 
- ;;         ("c" "Contacts" entry (file "~/Org/contacts.org")
 
- ;;          "* %(org-contacts-template-name)
 
- ;; :PROPERTIES:
 
- ;; :EMAIL: %(org-contacts-template-email)
 
- ;; :PHONE:
 
- ;; :ALIAS:
 
- ;; :NICKNAME:
 
- ;; :IGNORE:
 
- ;; :ICON:
 
- ;; :NOTE:
 
- ;; :ADDRESS:
 
- ;; :BIRTHDAY:
 
- ;; :END:")))
 
- ;;
 
- ;;; Code:
 
- (require 'cl-lib)
 
- (require 'org)
 
- (require 'gnus-util)
 
- (require 'gnus-art)
 
- (require 'mail-utils)
 
- (require 'org-agenda)
 
- (require 'org-capture)
 
- (require 'ol)
 
- (defgroup org-contacts nil
 
-   "Options about contacts management."
 
-   :group 'org)
 
- (defcustom org-contacts-files nil
 
-   "List of Org files to use as contacts source.
 
- When set to nil, all your Org files will be used."
 
-   :type '(repeat file)
 
-   :group 'org-contacts)
 
- (defcustom org-contacts-email-property "EMAIL"
 
-   "Name of the property for contact email address."
 
-   :type 'string
 
-   :group 'org-contacts)
 
- (defcustom org-contacts-tel-property "PHONE"
 
-   "Name of the property for contact phone number."
 
-   :type 'string
 
-   :group 'org-contacts)
 
- (defcustom org-contacts-address-property "ADDRESS"
 
-   "Name of the property for contact address."
 
-   :type 'string
 
-   :group 'org-contacts)
 
- (defcustom org-contacts-birthday-property "BIRTHDAY"
 
-   "Name of the property for contact birthday date."
 
-   :type 'string
 
-   :group 'org-contacts)
 
- (defcustom org-contacts-note-property "NOTE"
 
-   "Name of the property for contact note."
 
-   :type 'string
 
-   :group 'org-contacts)
 
- (defcustom org-contacts-alias-property "ALIAS"
 
-   "Name of the property for contact name alias."
 
-   :type 'string
 
-   :group 'org-contacts)
 
- (defcustom org-contacts-ignore-property "IGNORE"
 
-   "Name of the property, which values will be ignored when
 
- completing or exporting to vcard."
 
-   :type 'string
 
-   :group 'org-contacts)
 
- (defcustom org-contacts-birthday-format "Birthday: %l (%Y)"
 
-   "Format of the anniversary agenda entry.
 
- The following replacements are available:
 
-   %h - Heading name
 
-   %l - Link to the heading
 
-   %y - Number of year
 
-   %Y - Number of year (ordinal)"
 
-   :type 'string
 
-   :group 'org-contacts)
 
- (defcustom org-contacts-last-read-mail-property "LAST_READ_MAIL"
 
-   "Name of the property for contact last read email link storage."
 
-   :type 'string
 
-   :group 'org-contacts)
 
- (defcustom org-contacts-icon-property "ICON"
 
-   "Name of the property for contact icon."
 
-   :type 'string
 
-   :group 'org-contacts)
 
- (defcustom org-contacts-nickname-property "NICKNAME"
 
-   "Name of the property for IRC nickname match."
 
-   :type 'string
 
-   :group 'org-contacts)
 
- (defcustom org-contacts-icon-size 32
 
-   "Size of the contacts icons."
 
-   :type 'string
 
-   :group 'org-contacts)
 
- (defcustom org-contacts-icon-use-gravatar (fboundp 'gravatar-retrieve)
 
-   "Whether use Gravatar to fetch contact icons."
 
-   :type 'boolean
 
-   :group 'org-contacts)
 
- (defcustom org-contacts-completion-ignore-case t
 
-   "Ignore case when completing contacts."
 
-   :type 'boolean
 
-   :group 'org-contacts)
 
- (defcustom org-contacts-group-prefix "+"
 
-   "Group prefix."
 
-   :type 'string
 
-   :group 'org-contacts)
 
- (defcustom org-contacts-tags-props-prefix "#"
 
-   "Tags and properties prefix."
 
-   :type 'string
 
-   :group 'org-contacts)
 
- (defcustom org-contacts-matcher
 
-   (mapconcat #'identity
 
- 	     (mapcar (lambda (x) (concat x "<>\"\""))
 
- 		     (list org-contacts-email-property
 
- 			   org-contacts-alias-property
 
- 			   org-contacts-tel-property
 
- 			   org-contacts-address-property
 
- 			   org-contacts-birthday-property))
 
- 	     "|")
 
-   "Matching rule for finding heading that are contacts.
 
- This can be a tag name, or a property check."
 
-   :type 'string
 
-   :group 'org-contacts)
 
- (defcustom org-contacts-email-link-description-format "%s (%d)"
 
-   "Format used to store links to email.
 
- This overrides `org-email-link-description-format' if set."
 
-   :group 'org-contacts
 
-   :type 'string)
 
- (defcustom org-contacts-vcard-file "contacts.vcf"
 
-   "Default file for vcard export."
 
-   :group 'org-contacts
 
-   :type 'file)
 
- (defcustom org-contacts-enable-completion t
 
-   "Enable or not the completion in `message-mode' with `org-contacts'."
 
-   :group 'org-contacts
 
-   :type 'boolean)
 
- (defcustom org-contacts-complete-functions
 
-   '(org-contacts-complete-group org-contacts-complete-tags-props org-contacts-complete-name)
 
-   "List of functions used to complete contacts in `message-mode'."
 
-   :group 'org-contacts
 
-   :type 'hook)
 
- ;; Decalre external functions and variables
 
- (declare-function org-reverse-string "org")
 
- (declare-function diary-ordinal-suffix "ext:diary-lib")
 
- (declare-function wl-summary-message-number "ext:wl-summary")
 
- (declare-function wl-address-header-extract-address "ext:wl-address")
 
- (declare-function wl-address-header-extract-realname "ext:wl-address")
 
- (declare-function erc-buffer-list "ext:erc")
 
- (declare-function erc-get-channel-user-list "ext:erc")
 
- (declare-function google-maps-static-show "ext:google-maps-static")
 
- (declare-function elmo-message-field "ext:elmo-pipe")
 
- (declare-function std11-narrow-to-header "ext:std11")
 
- (declare-function std11-fetch-field "ext:std11")
 
- (defconst org-contacts-property-values-separators "[,; \f\t\n\r\v]+"
 
-   "The default value of separators for `org-contacts-split-property'.
 
- A regexp matching strings of whitespace, `,' and `;'.")
 
- (defvar org-contacts-keymap
 
-   (let ((map (make-sparse-keymap)))
 
-     (define-key map "M" 'org-contacts-view-send-email)
 
-     (define-key map "i" 'org-contacts-view-switch-to-irc-buffer)
 
-     map)
 
-   "The keymap used in `org-contacts' result list.")
 
- (defvar org-contacts-db nil
 
-   "Org Contacts database.")
 
- (defvar org-contacts-last-update nil
 
-   "Last time the Org Contacts database has been updated.")
 
- (defun org-contacts-files ()
 
-   "Return list of Org files to use for contact management."
 
-   (or org-contacts-files (org-agenda-files t 'ifmode)))
 
- (defun org-contacts-db-need-update-p ()
 
-   "Determine whether `org-contacts-db' needs to be refreshed."
 
-   (or (null org-contacts-last-update)
 
-       (cl-find-if (lambda (file)
 
- 		     (or (time-less-p org-contacts-last-update
 
- 				      (elt (file-attributes file) 5))))
 
- 		   (org-contacts-files))
 
-       (org-contacts-db-has-dead-markers-p org-contacts-db)))
 
- (defun org-contacts-db-has-dead-markers-p (org-contacts-db)
 
-   "Returns t if at least one dead marker is found in
 
- ORG-CONTACTS-DB. A dead marker in this case is a marker pointing
 
- to dead or no buffer."
 
-     ;; Scan contacts list looking for dead markers, and return t at first found.
 
-     (catch 'dead-marker-found
 
-       (while org-contacts-db
 
-         (unless (marker-buffer (nth 1 (car org-contacts-db)))
 
-           (throw 'dead-marker-found t))
 
-         (setq org-contacts-db (cdr org-contacts-db)))
 
-       nil))
 
- (defun org-contacts-db ()
 
-   "Return the latest Org Contacts Database."
 
-   (let* ((org--matcher-tags-todo-only nil)
 
- 	 (contacts-matcher (cdr (org-make-tags-matcher org-contacts-matcher)))
 
- 	 result)
 
-     (when (org-contacts-db-need-update-p)
 
-       (let ((progress-reporter
 
- 	     (make-progress-reporter "Updating Org Contacts Database..." 0 (length org-contacts-files)))
 
- 	    (i 0))
 
- 	(dolist (file (org-contacts-files))
 
- 	  (if (catch 'nextfile
 
-                 ;; if file doesn't exist and the user agrees to removing it
 
-                 ;; from org-agendas-list, 'nextfile is thrown.  Catch it here
 
-                 ;; and skip processing the file.
 
-                 ;;
 
-                 ;; TODO: suppose that the user has set an org-contacts-files
 
-                 ;; list that contains an element that doesn't exist in the
 
-                 ;; file system: in that case, the org-agenda-files list could
 
-                 ;; be updated (and saved to the customizations of the user) if
 
-                 ;; it contained the same file even though the org-agenda-files
 
-                 ;; list wasn't actually used.  I don't think it is normal that
 
-                 ;; org-contacts updates org-agenda-files in this case, but
 
-                 ;; short of duplicating org-check-agenda-files and
 
-                 ;; org-remove-files, I don't know how to avoid it.
 
-                 ;;
 
-                 ;; A side effect of the TODO is that the faulty
 
-                 ;; org-contacts-files list never gets updated and thus the
 
-                 ;; user is always queried about the missing files when
 
-                 ;; org-contacts-db-need-update-p returns true.
 
-                 (org-check-agenda-file file))
 
-               (message "Skipped %s removed from org-agenda-files list."
 
-                        (abbreviate-file-name file))
 
- 	    (with-current-buffer (org-get-agenda-file-buffer file)
 
- 	      (unless (eq major-mode 'org-mode)
 
- 		(error "File %s is not in `org-mode'" file))
 
- 	      (setf result
 
- 		    (append result
 
- 			    (org-scan-tags 'org-contacts-at-point
 
- 					   contacts-matcher
 
- 					   org--matcher-tags-todo-only)))))
 
- 	  (progress-reporter-update progress-reporter (setq i (1+ i))))
 
- 	(setf org-contacts-db result
 
- 	      org-contacts-last-update (current-time))
 
- 	(progress-reporter-done progress-reporter)))
 
-     org-contacts-db))
 
- (defun org-contacts-at-point (&optional pom)
 
-   "Return the contacts at point-or-marker POM or current position
 
- if nil."
 
-   (setq pom (or pom (point)))
 
-   (org-with-point-at pom
 
-     (list (org-get-heading t) (set-marker (make-marker) pom) (org-entry-properties pom 'all))))
 
- (defun org-contacts-filter (&optional name-match tags-match prop-match)
 
-   "Search for a contact matching any of NAME-MATCH, TAGS-MATCH, PROP-MATCH.
 
- If all match values are nil, return all contacts.
 
- The optional PROP-MATCH argument is a single (PROP . VALUE) cons
 
- cell corresponding to the contact properties.
 
- "
 
-   (if (and (null name-match)
 
- 	   (null prop-match)
 
- 	   (null tags-match))
 
-       (org-contacts-db)
 
-     (cl-loop for contact in (org-contacts-db)
 
- 	     if (or
 
- 		 (and name-match
 
- 		      (string-match-p name-match
 
- 					  (first contact)))
 
- 		 (and prop-match
 
- 		      (cl-find-if (lambda (prop)
 
- 				     (and (string= (car prop-match) (car prop))
 
- 					  (string-match-p (cdr prop-match) (cdr prop))))
 
- 				   (caddr contact)))
 
- 		 (and tags-match
 
- 		      (cl-find-if (lambda (tag)
 
- 				     (string-match-p tags-match tag))
 
- 				   (org-split-string
 
- 				    (or (cdr (assoc-string "ALLTAGS" (caddr contact))) "") ":"))))
 
- 	     collect contact)))
 
- (when (not (fboundp 'completion-table-case-fold))
 
-   ;; That function is new in Emacs 24...
 
-   (defun completion-table-case-fold (table &optional dont-fold)
 
-     (lambda (string pred action)
 
-       (let ((completion-ignore-case (not dont-fold)))
 
- 	(complete-with-action action table string pred)))))
 
- (defun org-contacts-try-completion-prefix (to-match collection &optional predicate)
 
-   "Custom implementation of `try-completion'.
 
- This version works only with list and alist and it looks at all
 
- prefixes rather than just the beginning of the string."
 
-   (cl-loop with regexp = (concat "\\b" (regexp-quote to-match))
 
- 	   with ret = nil
 
- 	   with ret-start = nil
 
- 	   with ret-end = nil
 
- 	   for el in collection
 
- 	   for string = (if (listp el) (car el) el)
 
- 	   for start = (when (or (null predicate) (funcall predicate string))
 
- 			 (string-match regexp string))
 
- 	   if start
 
- 	   do (let ((end (match-end 0))
 
- 		    (len (length string)))
 
- 		(if (= end len)
 
- 		    (cl-return t)
 
- 		  (cl-destructuring-bind (string start end)
 
- 		      (if (null ret)
 
- 			  (values string start end)
 
- 			(org-contacts-common-substring
 
- 			 ret ret-start ret-end
 
- 			 string start end))
 
- 		    (setf ret string
 
- 			  ret-start start
 
- 			  ret-end end))))
 
- 	   finally (cl-return
 
- 		    (replace-regexp-in-string "\\`[ \t\n]*" "" ret))))
 
- (defun org-contacts-compare-strings (s1 start1 end1 s2 start2 end2 &optional ignore-case)
 
-   "Compare the contents of two strings, using `compare-strings'.
 
- This function works like `compare-strings' excepted that it
 
- returns a cons.
 
- - The CAR is the number of characters that match at the beginning.
 
- - The CDR is T is the two strings are the same and NIL otherwise."
 
-   (let ((ret (compare-strings s1 start1 end1 s2 start2 end2 ignore-case)))
 
-     (if (eq ret t)
 
- 	(cons (or end1 (length s1)) t)
 
-       (cons (1- (abs ret)) nil))))
 
- (defun org-contacts-common-substring (s1 start1 end1 s2 start2 end2)
 
-   "Extract the common substring between S1 and S2.
 
- This function extracts the common substring between S1 and S2 and
 
- adjust the part that remains common.
 
- START1 and END1 delimit the part in S1 that we know is common
 
- between the two strings. This applies to START2 and END2 for S2.
 
- This function returns a list whose contains:
 
- - The common substring found.
 
- - The new value of the start of the known inner substring.
 
- - The new value of the end of the known inner substring."
 
-   ;; Given two strings:
 
-   ;; s1: "foo bar baz"
 
-   ;; s2: "fooo bar baz"
 
-   ;; and the inner substring is "bar"
 
-   ;; then: start1 = 4, end1 = 6, start2 = 5, end2 = 7
 
-   ;;
 
-   ;; To find the common substring we will compare two substrings:
 
-   ;; " oof" and " ooof" to find the beginning of the common substring.
 
-   ;; " baz" and " baz" to find the end of the common substring.
 
-   (let* ((len1 (length s1))
 
- 	 (start1 (or start1 0))
 
- 	 (end1 (or end1 len1))
 
- 	 (len2 (length s2))
 
- 	 (start2 (or start2 0))
 
- 	 (end2 (or end2 len2))
 
- 	 (new-start (car (org-contacts-compare-strings
 
- 			  (substring (org-reverse-string s1) (- len1 start1)) nil nil
 
- 			  (substring (org-reverse-string s2) (- len2 start2)) nil nil)))
 
- 	 (new-end (+ end1 (car (org-contacts-compare-strings
 
- 				(substring s1 end1) nil nil
 
- 				(substring s2 end2) nil nil)))))
 
-     (list (substring s1 (- start1 new-start) new-end)
 
- 	  new-start
 
- 	  (+ new-start (- end1 start1)))))
 
- (defun org-contacts-all-completions-prefix (to-match collection &optional predicate)
 
-   "Custom version of `all-completions'.
 
- This version works only with list and alist and it looks at all
 
- prefixes rather than just the beginning of the string."
 
-   (cl-loop with regexp = (concat "\\b" (regexp-quote to-match))
 
- 	   for el in collection
 
- 	   for string = (if (listp el) (car el) el)
 
- 	   for match? = (when (and (or (null predicate) (funcall predicate string)))
 
- 			  (string-match regexp string))
 
- 	   if match?
 
- 	   collect (progn
 
- 		     (let ((end (match-end 0)))
 
- 		       (org-no-properties string)
 
- 		       (when (< end (length string))
 
- 			 ;; Here we add a text property that will be used
 
- 			 ;; later to highlight the character right after
 
- 			 ;; the common part between each addresses.
 
- 			 ;; See `org-contacts-display-sort-function'.
 
- 			 (put-text-property end (1+ end) 'org-contacts-prefix 't string)))
 
- 		     string)))
 
- (defun org-contacts-make-collection-prefix (collection)
 
-   "Make a collection function from COLLECTION which will match on prefixes."
 
-   (lexical-let ((collection collection))
 
-     (lambda (string predicate flag)
 
-       (cond ((eq flag nil)
 
- 	     (org-contacts-try-completion-prefix string collection predicate))
 
- 	    ((eq flag t)
 
- 	     ;; `org-contacts-all-completions-prefix' has already been
 
- 	     ;; used to compute `all-completions'.
 
- 	     collection)
 
- 	    ((eq flag 'lambda)
 
- 	     (org-contacts-test-completion-prefix string collection predicate))
 
- 	    ((and (listp flag) (eq (car flag) 'boundaries))
 
- 	     (cl-destructuring-bind (to-ignore &rest suffix)
 
- 		 flag
 
- 	       (org-contacts-boundaries-prefix string collection predicate suffix)))
 
- 	    ((eq flag 'metadata)
 
- 	     (org-contacts-metadata-prefix string collection predicate))
 
- 	    (t nil			; operation unsupported
 
- 	       )))))
 
- (defun org-contacts-display-sort-function (completions)
 
-   "Sort function for contacts display."
 
-   (mapcar (lambda (string)
 
- 	    (cl-loop with len = (1- (length string))
 
- 		     for i upfrom 0 to len
 
- 		     if (memq 'org-contacts-prefix
 
- 			      (text-properties-at i string))
 
- 		     do (set-text-properties
 
- 			 i (1+ i)
 
- 			 (list 'font-lock-face
 
- 			       (if (char-equal (aref string i)
 
- 					       (string-to-char " "))
 
- 				   ;; Spaces can't be bold.
 
- 				   'underline
 
- 				 'bold)) string)
 
- 		     else
 
- 		     do (set-text-properties i (1+ i) nil string)
 
- 		     finally (cl-return string)))
 
- 	  completions))
 
- (defun org-contacts-test-completion-prefix (string collection predicate)
 
-   (cl-find-if (lambda (el)
 
- 		(and (or (null predicate) (funcall predicate el))
 
- 		     (string= string el)))
 
- 	      collection))
 
- (defun org-contacts-boundaries-prefix (string collection predicate suffix)
 
-   (list* 'boundaries (completion-boundaries string collection predicate suffix)))
 
- (defun org-contacts-metadata-prefix (string collection predicate)
 
-   '(metadata .
 
- 	     ((cycle-sort-function . org-contacts-display-sort-function)
 
- 	      (display-sort-function . org-contacts-display-sort-function))))
 
- (defun org-contacts-complete-group (start end string)
 
-   "Complete text at START from a group.
 
- A group FOO is composed of contacts with the tag FOO."
 
-   (let* ((completion-ignore-case org-contacts-completion-ignore-case)
 
- 	 (group-completion-p (string-match-p
 
- 			      (concat "^" org-contacts-group-prefix) string)))
 
-     (when group-completion-p
 
-       (let ((completion-list
 
- 	     (all-completions
 
- 	      string
 
- 	      (mapcar (lambda (group)
 
- 			(propertize (concat org-contacts-group-prefix group)
 
- 				    'org-contacts-group group))
 
- 		      (org-uniquify
 
- 		       (cl-loop for contact in (org-contacts-filter)
 
- 				nconc (org-split-string
 
- 				       (or (cdr (assoc-string "ALLTAGS" (caddr contact))) "") ":")))))))
 
- 	(list start end
 
- 	      (if (= (length completion-list) 1)
 
- 		  ;; We've found the correct group, returns the address
 
- 		  (lexical-let ((tag (get-text-property 0 'org-contacts-group
 
- 							(car completion-list))))
 
- 		    (lambda (string pred &optional to-ignore)
 
- 		      (mapconcat 'identity
 
- 				 (cl-loop for contact in (org-contacts-filter
 
- 							  nil
 
- 							  tag)
 
- 					  ;; The contact name is always the car of the assoc-list
 
- 					  ;; returned by `org-contacts-filter'.
 
- 					  for contact-name = (car contact)
 
- 					  ;; Grab the first email of the contact
 
- 					  for email = (org-contacts-strip-link
 
- 						       (or (car (org-contacts-split-property
 
- 								 (or
 
- 								  (cdr (assoc-string org-contacts-email-property
 
- 										     (cl-caddr contact)))
 
- 								  ""))) ""))
 
- 					  ;; If the user has an email address, append USER <EMAIL>.
 
- 					  if email collect (org-contacts-format-email contact-name email))
 
- 				 ", ")))
 
- 		;; We haven't found the correct group
 
- 		(completion-table-case-fold completion-list
 
- 					    (not org-contacts-completion-ignore-case))))))))
 
- (defun org-contacts-complete-tags-props (start end string)
 
-   "Insert emails that match the tags expression.
 
- For example: FOO-BAR will match entries tagged with FOO but not
 
- with BAR.
 
- See (org) Matching tags and properties for a complete
 
- description."
 
-   (let* ((completion-ignore-case org-contacts-completion-ignore-case)
 
- 	 (completion-p (string-match-p
 
- 			(concat "^" org-contacts-tags-props-prefix) string)))
 
-     (when completion-p
 
-       (let ((result
 
- 	     (mapconcat
 
- 	      'identity
 
- 	      (cl-loop for contact in (org-contacts-db)
 
- 		       for contact-name = (car contact)
 
- 		       for email = (org-contacts-strip-link (or (car (org-contacts-split-property
 
- 								      (or
 
- 								       (cdr (assoc-string org-contacts-email-property
 
- 											  (cl-caddr contact)))
 
- 								       ""))) ""))
 
- 		       for tags = (cdr (assoc "TAGS" (nth 2 contact)))
 
- 		       for tags-list = (if tags
 
- 					   (split-string (substring (cdr (assoc "TAGS" (nth 2 contact))) 1 -1) ":")
 
- 					 '())
 
- 		       for marker = (nth 1 contact)
 
- 		       if (with-current-buffer (marker-buffer marker)
 
- 			    (save-excursion
 
- 			      (goto-char marker)
 
- 			      (let (todo-only)
 
- 				(eval (cdr (org-make-tags-matcher (cl-subseq string 1)))))))
 
- 		       collect (org-contacts-format-email contact-name email))
 
- 	      ",")))
 
- 	(when (not (string= "" result))
 
- 	  ;; return (start end function)
 
- 	  (lexical-let* ((to-return result))
 
- 	    (list start end
 
- 		  (lambda (string pred &optional to-ignore) to-return))))))))
 
- (defun org-contacts-remove-ignored-property-values (ignore-list list)
 
-   "Remove all ignore-list's elements from list and you can use
 
-    regular expressions in the ignore list."
 
-   (cl-remove-if (lambda (el)
 
- 		  (cl-find-if (lambda (x)
 
- 				 (string-match-p x el))
 
- 			       ignore-list))
 
- 		list))
 
- (defun org-contacts-complete-name (start end string)
 
-   "Complete text at START with a user name and email."
 
-   (let* ((completion-ignore-case org-contacts-completion-ignore-case)
 
-          (completion-list
 
- 	  (cl-loop for contact in (org-contacts-filter)
 
- 		   ;; The contact name is always the car of the assoc-list
 
- 		   ;; returned by `org-contacts-filter'.
 
- 		   for contact-name = (car contact)
 
- 		   ;; Build the list of the email addresses which has
 
- 		   ;; been expired
 
- 		   for ignore-list = (org-contacts-split-property
 
- 				      (or (cdr (assoc-string org-contacts-ignore-property
 
- 							     (nth 2 contact))) ""))
 
- 		   ;; Build the list of the user email addresses.
 
- 		   for email-list = (org-contacts-remove-ignored-property-values
 
- 				     ignore-list
 
- 				     (org-contacts-split-property
 
- 				      (or (cdr (assoc-string org-contacts-email-property
 
- 							     (nth 2 contact))) "")))
 
- 		   ;; If the user has email addresses…
 
- 		   if email-list
 
- 		   ;; … append a list of USER <EMAIL>.
 
- 		   nconc (cl-loop for email in email-list
 
- 				  collect (org-contacts-format-email contact-name (org-contacts-strip-link email)))))
 
- 	 (completion-list (org-contacts-all-completions-prefix
 
- 			   string
 
- 			   (org-uniquify completion-list))))
 
-     (when completion-list
 
-       (list start end
 
- 	    (org-contacts-make-collection-prefix completion-list)))))
 
- (defun org-contacts-message-complete-function (&optional start)
 
-   "Function used in `completion-at-point-functions' in `message-mode'."
 
-   ;; Avoid to complete in `post-command-hook'.
 
-   (when completion-in-region-mode
 
-     (remove-hook 'post-command-hook #'completion-in-region--postch))
 
-   (let ((mail-abbrev-mode-regexp
 
-          "^\\(Resent-To\\|To\\|B?Cc\\|Reply-To\\|From\\|Mail-Followup-To\\|Mail-Copies-To\\|Disposition-Notification-To\\|Return-Receipt-To\\):"))
 
-     (when (mail-abbrev-in-expansion-header-p)
 
-       (lexical-let*
 
- 	  ((end (point))
 
- 	   (start (or start
 
- 		      (save-excursion
 
- 			(re-search-backward "\\(\\`\\|[\n:,]\\)[ \t]*")
 
- 			(goto-char (match-end 0))
 
- 			(point))))
 
- 	   (string (buffer-substring start end)))
 
- 	(run-hook-with-args-until-success
 
- 	 'org-contacts-complete-functions start end string)))))
 
- (defun org-contacts-gnus-get-name-email ()
 
-   "Get name and email address from Gnus message."
 
-   (if (gnus-alive-p)
 
-       (gnus-with-article-headers
 
-         (mail-extract-address-components
 
-          (or (mail-fetch-field "From") "")))))
 
- (defun org-contacts-gnus-article-from-get-marker ()
 
-   "Return a marker for a contact based on From."
 
-   (let* ((address (org-contacts-gnus-get-name-email))
 
-          (name (car address))
 
-          (email (cadr address)))
 
-     (cl-cadar (or (org-contacts-filter
 
- 		   nil
 
- 		   nil
 
- 		   (cons org-contacts-email-property (concat "\\b" (regexp-quote email) "\\b")))
 
- 		  (when name
 
- 		    (org-contacts-filter
 
- 		     (concat "^" name "$")))))))
 
- (defun org-contacts-gnus-article-from-goto ()
 
-   "Go to contact in the From address of current Gnus message."
 
-   (interactive)
 
-   (let ((marker (org-contacts-gnus-article-from-get-marker)))
 
-     (when marker
 
-       (switch-to-buffer-other-window (marker-buffer marker))
 
-       (goto-char marker)
 
-       (when (eq major-mode 'org-mode) (org-show-context 'agenda)))))
 
- (with-no-warnings (defvar date)) ;; unprefixed, from calendar.el
 
- (defun org-contacts-anniversaries (&optional field format)
 
-   "Compute FIELD anniversary for each contact, returning FORMAT.
 
- Default FIELD value is \"BIRTHDAY\".
 
- Format is a string matching the following format specification:
 
-   %h - Heading name
 
-   %l - Link to the heading
 
-   %y - Number of year
 
-   %Y - Number of year (ordinal)"
 
-   (let ((calendar-date-style 'american)
 
-         (entry ""))
 
-     (unless format (setq format org-contacts-birthday-format))
 
-     (cl-loop for contact in (org-contacts-filter)
 
- 	     for anniv = (let ((anniv (cdr (assoc-string
 
- 					    (or field org-contacts-birthday-property)
 
- 					    (nth 2 contact)))))
 
- 			   (when anniv
 
- 			     (calendar-gregorian-from-absolute
 
- 			      (org-time-string-to-absolute anniv))))
 
- 	     ;; Use `diary-anniversary' to compute anniversary.
 
- 	     if (and anniv (apply 'diary-anniversary anniv))
 
- 	     collect (format-spec format
 
- 				  `((?l . ,(org-with-point-at (cadr contact) (org-store-link nil)))
 
- 				    (?h . ,(car contact))
 
- 				    (?y . ,(- (calendar-extract-year date)
 
- 					      (calendar-extract-year anniv)))
 
- 				    (?Y . ,(let ((years (- (calendar-extract-year date)
 
- 							   (calendar-extract-year anniv))))
 
- 					     (format "%d%s" years (diary-ordinal-suffix years)))))))))
 
- (defun org-completing-read-date (prompt collection
 
-                                         &optional predicate require-match initial-input
 
-                                         hist def inherit-input-method)
 
-   "Like `completing-read' but reads a date.
 
- Only PROMPT and DEF are really used."
 
-   (org-read-date nil nil nil prompt nil def))
 
- (add-to-list 'org-property-set-functions-alist
 
-              `(,org-contacts-birthday-property . org-completing-read-date))
 
- (defun org-contacts-template-name (&optional return-value)
 
-   "Try to return the contact name for a template.
 
- If not found return RETURN-VALUE or something that would ask the user."
 
-   (or (car (org-contacts-gnus-get-name-email))
 
-       return-value
 
-       "%^{Name}"))
 
- (defun org-contacts-template-email (&optional return-value)
 
-   "Try to return the contact email for a template.
 
- If not found return RETURN-VALUE or something that would ask the user."
 
-   (or (cadr (org-contacts-gnus-get-name-email))
 
-       return-value
 
-       (concat "%^{" org-contacts-email-property "}p")))
 
- (defun org-contacts-gnus-store-last-mail ()
 
-   "Store a link between mails and contacts.
 
- This function should be called from `gnus-article-prepare-hook'."
 
-   (let ((marker (org-contacts-gnus-article-from-get-marker)))
 
-     (when marker
 
-       (with-current-buffer (marker-buffer marker)
 
-         (save-excursion
 
-           (goto-char marker)
 
-           (let* ((org-email-link-description-format (or org-contacts-email-link-description-format
 
-                                                         org-email-link-description-format))
 
-                  (link (gnus-with-article-buffer (org-store-link nil))))
 
-             (org-set-property org-contacts-last-read-mail-property link)))))))
 
- (defun org-contacts-icon-as-string ()
 
-   "Return the contact icon as a string."
 
-   (let ((image (org-contacts-get-icon)))
 
-     (concat
 
-      (propertize "-" 'display
 
-                  (append
 
-                   (if image
 
-                       image
 
-                     `'(space :width (,org-contacts-icon-size)))
 
-                   '(:ascent center)))
 
-      " ")))
 
- ;;;###autoload
 
- (defun org-contacts (name)
 
-   "Create agenda view for contacts matching NAME."
 
-   (interactive (list (read-string "Name: ")))
 
-   (let ((org-agenda-files (org-contacts-files))
 
-         (org-agenda-skip-function
 
-          (lambda () (org-agenda-skip-if nil `(notregexp ,name))))
 
-         (org-agenda-prefix-format (propertize
 
- 				   "%(org-contacts-icon-as-string)% s%(org-contacts-irc-number-of-unread-messages) "
 
- 				   'keymap org-contacts-keymap))
 
-         (org-agenda-overriding-header
 
-          (or org-agenda-overriding-header
 
-              (concat "List of contacts matching `" name "':"))))
 
-     (setq org-agenda-skip-regexp name)
 
-     (org-tags-view nil org-contacts-matcher)
 
-     (with-current-buffer org-agenda-buffer-name
 
-       (setq org-agenda-redo-command
 
-             (list 'org-contacts name)))))
 
- (defun org-contacts-completing-read (prompt
 
-                                      &optional predicate
 
-                                      initial-input hist def inherit-input-method)
 
-   "Call `completing-read' with contacts name as collection."
 
-   (org-completing-read
 
-    prompt (org-contacts-filter) predicate t initial-input hist def inherit-input-method))
 
- (defun org-contacts-format-name (name)
 
-   "Trim any local formatting to get a bare NAME."
 
-   ;; Remove radio targets characters
 
-   (replace-regexp-in-string org-radio-target-regexp "\\1" name))
 
- (defun org-contacts-format-email (name email)
 
-   "Format an EMAIL address corresponding to NAME."
 
-   (unless email
 
-     (error "`email' cannot be nul"))
 
-   (if name
 
-       (concat (org-contacts-format-name name) " <" email ">")
 
-     email))
 
- (defun org-contacts-check-mail-address (mail)
 
-   "Add MAIL address to contact at point if it does not have it."
 
-   (let ((mails (org-entry-get (point) org-contacts-email-property)))
 
-     (unless (member mail (split-string mails))
 
-       (when (yes-or-no-p
 
-              (format "Do you want to add this address to %s?" (org-get-heading t)))
 
-         (org-set-property org-contacts-email-property (concat mails " " mail))))))
 
- (defun org-contacts-gnus-check-mail-address ()
 
-   "Check that contact has the current address recorded.
 
- This function should be called from `gnus-article-prepare-hook'."
 
-   (let ((marker (org-contacts-gnus-article-from-get-marker)))
 
-     (when marker
 
-       (org-with-point-at marker
 
-         (org-contacts-check-mail-address (cadr (org-contacts-gnus-get-name-email)))))))
 
- (defun org-contacts-gnus-insinuate ()
 
-   "Add some hooks for Gnus user.
 
- This adds `org-contacts-gnus-check-mail-address' and
 
- `org-contacts-gnus-store-last-mail' to
 
- `gnus-article-prepare-hook'.  It also adds a binding on `;' in
 
- `gnus-summary-mode-map' to `org-contacts-gnus-article-from-goto'"
 
-   (require 'gnus)
 
-   (require 'gnus-art)
 
-   (define-key gnus-summary-mode-map ";" 'org-contacts-gnus-article-from-goto)
 
-   (add-hook 'gnus-article-prepare-hook 'org-contacts-gnus-check-mail-address)
 
-   (add-hook 'gnus-article-prepare-hook 'org-contacts-gnus-store-last-mail))
 
- (defun org-contacts-setup-completion-at-point ()
 
-   "Add `org-contacts-message-complete-function' as a new function
 
- to complete the thing at point."
 
-   (add-to-list 'completion-at-point-functions
 
- 	       'org-contacts-message-complete-function))
 
- (defun org-contacts-unload-hook ()
 
-   (remove-hook 'message-mode-hook 'org-contacts-setup-completion-at-point))
 
- (when (and org-contacts-enable-completion
 
- 	   (boundp 'completion-at-point-functions))
 
-   (add-hook 'message-mode-hook 'org-contacts-setup-completion-at-point))
 
- (defun org-contacts-wl-get-from-header-content ()
 
-   "Retrieve the content of the `From' header of an email.
 
- Works from wl-summary-mode and mime-view-mode - that is while viewing email.
 
- Depends on Wanderlust been loaded."
 
-   (with-current-buffer (org-capture-get :original-buffer)
 
-     (cond
 
-      ((eq major-mode 'wl-summary-mode) (when (and (boundp 'wl-summary-buffer-elmo-folder)
 
- 						  wl-summary-buffer-elmo-folder)
 
-                                          (elmo-message-field
 
-                                           wl-summary-buffer-elmo-folder
 
-                                           (wl-summary-message-number)
 
-                                           'from)))
 
-      ((eq major-mode 'mime-view-mode) (std11-narrow-to-header)
 
-       (prog1
 
- 	  (std11-fetch-field "From")
 
- 	(widen))))))
 
- (defun org-contacts-wl-get-name-email ()
 
-   "Get name and email address from Wanderlust email.
 
- See `org-contacts-wl-get-from-header-content' for limitations."
 
-   (let ((from (org-contacts-wl-get-from-header-content)))
 
-     (when from
 
-       (list (wl-address-header-extract-realname from)
 
- 	    (wl-address-header-extract-address from)))))
 
- (defun org-contacts-template-wl-name (&optional return-value)
 
-   "Try to return the contact name for a template from wl.
 
- If not found, return RETURN-VALUE or something that would ask the
 
- user."
 
-   (or (car (org-contacts-wl-get-name-email))
 
-       return-value
 
-       "%^{Name}"))
 
- (defun org-contacts-template-wl-email (&optional return-value)
 
-   "Try to return the contact email for a template from Wanderlust.
 
- If not found return RETURN-VALUE or something that would ask the user."
 
-   (or (cadr (org-contacts-wl-get-name-email))
 
-       return-value
 
-       (concat "%^{" org-contacts-email-property "}p")))
 
- (defun org-contacts-view-send-email (&optional ask)
 
-   "Send email to the contact at point.
 
- If ASK is set, ask for the email address even if there's only one
 
- address."
 
-   (interactive "P")
 
-   (let ((marker (org-get-at-bol 'org-hd-marker)))
 
-     (org-with-point-at marker
 
-       (let ((emails (org-entry-get (point) org-contacts-email-property)))
 
-         (if emails
 
-             (let ((email-list (org-contacts-split-property emails)))
 
-               (if (and (= (length email-list) 1) (not ask))
 
-                   (compose-mail (org-contacts-format-email
 
-                                  (org-get-heading t) emails))
 
-                 (let ((email (completing-read "Send mail to which address: " email-list)))
 
- 		  (setq email (org-contacts-strip-link email))
 
-                   (org-contacts-check-mail-address email)
 
-                   (compose-mail (org-contacts-format-email (org-get-heading t) email)))))
 
-           (error (format "This contact has no mail address set (no %s property)"
 
-                          org-contacts-email-property)))))))
 
- (defun org-contacts-get-icon (&optional pom)
 
-   "Get icon for contact at POM."
 
-   (setq pom (or pom (point)))
 
-   (catch 'icon
 
-     ;; Use `org-contacts-icon-property'
 
-     (let ((image-data (org-entry-get pom org-contacts-icon-property)))
 
-       (when image-data
 
-         (throw 'icon
 
-                (if (fboundp 'gnus-rescale-image)
 
-                    (gnus-rescale-image (create-image image-data)
 
-                                        (cons org-contacts-icon-size org-contacts-icon-size))
 
-                  (create-image image-data)))))
 
-     ;; Next, try Gravatar
 
-     (when org-contacts-icon-use-gravatar
 
-       (let* ((gravatar-size org-contacts-icon-size)
 
-              (email-list (org-entry-get pom org-contacts-email-property))
 
-              (gravatar
 
-               (when email-list
 
-                 (loop for email in (org-contacts-split-property email-list)
 
-                       for gravatar = (gravatar-retrieve-synchronously (org-contacts-strip-link email))
 
-                       if (and gravatar
 
-                               (not (eq gravatar 'error)))
 
-                       return gravatar))))
 
-         (when gravatar (throw 'icon gravatar))))))
 
- (defun org-contacts-irc-buffer (&optional pom)
 
-   "Get the IRC buffer associated with the entry at POM."
 
-   (setq pom (or pom (point)))
 
-   (let ((nick (org-entry-get pom org-contacts-nickname-property)))
 
-     (when nick
 
-       (let ((buffer (get-buffer nick)))
 
-         (when buffer
 
-           (with-current-buffer buffer
 
-             (when (eq major-mode 'erc-mode)
 
-               buffer)))))))
 
- (defun org-contacts-irc-number-of-unread-messages (&optional pom)
 
-   "Return the number of unread messages for contact at POM."
 
-   (when (boundp 'erc-modified-channels-alist)
 
-     (let ((number (cadr (assoc (org-contacts-irc-buffer pom) erc-modified-channels-alist))))
 
-       (if number
 
-           (format (concat "%3d unread message" (if (> number 1) "s" " ") " ") number)
 
-         (make-string 21 ? )))))
 
- (defun org-contacts-view-switch-to-irc-buffer ()
 
-   "Switch to the IRC buffer of the current contact if it has one."
 
-   (interactive)
 
-   (let ((marker (org-get-at-bol 'org-hd-marker)))
 
-     (org-with-point-at marker
 
-       (switch-to-buffer-other-window (org-contacts-irc-buffer)))))
 
- (defun org-contacts-completing-read-nickname (prompt collection
 
-                                                      &optional predicate require-match initial-input
 
-                                                      hist def inherit-input-method)
 
-   "Like `completing-read' but reads a nickname."
 
-   (org-completing-read prompt (append collection (erc-nicknames-list)) predicate require-match
 
-                        initial-input hist def inherit-input-method))
 
- (defun erc-nicknames-list ()
 
-   "Return all nicknames of all ERC buffers."
 
-   (loop for buffer in (erc-buffer-list)
 
- 	nconc (with-current-buffer buffer
 
- 		(loop for user-entry in (mapcar 'car (erc-get-channel-user-list))
 
- 		      collect (elt user-entry 1)))))
 
- (add-to-list 'org-property-set-functions-alist
 
-              `(,org-contacts-nickname-property . org-contacts-completing-read-nickname))
 
- (defun org-contacts-vcard-escape (str)
 
-   "Escape ; , and \n in STR for the VCard format."
 
-   ;; Thanks to this library for the regexp:
 
-   ;; http://www.emacswiki.org/cgi-bin/wiki/bbdb-vcard-export.el
 
-   (when str
 
-     (replace-regexp-in-string
 
-      "\n" "\\\\n"
 
-      (replace-regexp-in-string "\\(;\\|,\\|\\\\\\)" "\\\\\\1" str))))
 
- (defun org-contacts-vcard-encode-name (name)
 
-   "Try to encode NAME as VCard's N property.
 
- The N property expects
 
-   FamilyName;GivenName;AdditionalNames;Prefix;Postfix.
 
- Org-contacts does not specify how to encode the name.  So we try
 
- to do our best."
 
-   (concat (replace-regexp-in-string "\\(\\w+\\) \\(.*\\)" "\\2;\\1" name) ";;;"))
 
- (defun org-contacts-vcard-format (contact)
 
-   "Formats CONTACT in VCard 3.0 format."
 
-   (let* ((properties (nth 2 contact))
 
- 	 (name (org-contacts-vcard-escape (car contact)))
 
- 	 (n (org-contacts-vcard-encode-name name))
 
- 	 (email (cdr (assoc-string org-contacts-email-property properties)))
 
- 	 (tel (cdr (assoc-string org-contacts-tel-property properties)))
 
- 	 (ignore-list (cdr (assoc-string org-contacts-ignore-property properties)))
 
- 	 (ignore-list (when ignore-list
 
- 			(org-contacts-split-property ignore-list)))
 
- 	 (note (cdr (assoc-string org-contacts-note-property properties)))
 
- 	 (bday (org-contacts-vcard-escape (cdr (assoc-string org-contacts-birthday-property properties))))
 
- 	 (addr (cdr (assoc-string org-contacts-address-property properties)))
 
- 	 (nick (org-contacts-vcard-escape (cdr (assoc-string org-contacts-nickname-property properties))))
 
- 	 (head (format "BEGIN:VCARD\nVERSION:3.0\nN:%s\nFN:%s\n" n name))
 
- 	 emails-list result phones-list)
 
-     (concat head
 
- 	    (when email (progn
 
- 			  (setq emails-list (org-contacts-remove-ignored-property-values ignore-list (org-contacts-split-property email)))
 
- 			  (setq result "")
 
- 			  (while emails-list
 
- 			    (setq result (concat result  "EMAIL:" (org-contacts-strip-link (car emails-list)) "\n"))
 
- 			    (setq emails-list (cdr emails-list)))
 
- 			  result))
 
- 	    (when addr
 
- 	      (format "ADR:;;%s\n" (replace-regexp-in-string "\\, ?" ";" addr)))
 
- 	    (when tel (progn
 
- 			(setq phones-list (org-contacts-remove-ignored-property-values ignore-list (org-contacts-split-property tel)))
 
- 			(setq result "")
 
- 			(while phones-list
 
- 			  (setq result (concat result  "TEL:" (org-contacts-strip-link (org-link-unescape (car phones-list))) "\n"))
 
- 			  (setq phones-list (cdr phones-list)))
 
- 			result))
 
- 	    (when bday
 
- 	      (let ((cal-bday (calendar-gregorian-from-absolute (org-time-string-to-absolute bday))))
 
- 		(format "BDAY:%04d-%02d-%02d\n"
 
- 			(calendar-extract-year cal-bday)
 
- 			(calendar-extract-month cal-bday)
 
- 			(calendar-extract-day cal-bday))))
 
- 	    (when nick (format "NICKNAME:%s\n" nick))
 
- 	    (when note (format "NOTE:%s\n" note))
 
- 	    "END:VCARD\n\n")))
 
- (defun org-contacts-export-as-vcard (&optional name file to-buffer)
 
-   "Export org contacts to V-Card 3.0.
 
- By default, all contacts are exported to `org-contacts-vcard-file'.
 
- When NAME is \\[universal-argument], prompts for a contact name.
 
- When NAME is \\[universal-argument] \\[universal-argument],
 
- prompts for a contact name and a file name where to export.
 
- When NAME is \\[universal-argument] \\[universal-argument]
 
- \\[universal-argument], prompts for a contact name and a buffer where to export.
 
- If the function is not called interactively, all parameters are
 
- passed to `org-contacts-export-as-vcard-internal'."
 
-   (interactive "P")
 
-   (when (called-interactively-p 'any)
 
-     (cl-psetf name
 
- 	      (when name
 
- 		(read-string "Contact name: "
 
- 			     (nth 0 (org-contacts-at-point))))
 
- 	      file
 
- 	      (when (equal name '(16))
 
- 		(read-file-name "File: " nil org-contacts-vcard-file))
 
- 	      to-buffer
 
- 	      (when (equal name '(64))
 
- 		(read-buffer "Buffer: "))))
 
-   (org-contacts-export-as-vcard-internal name file to-buffer))
 
- (defun org-contacts-export-as-vcard-internal (&optional name file to-buffer)
 
-   "Export all contacts matching NAME as VCard 3.0.
 
- If TO-BUFFER is nil, the content is written to FILE or
 
- `org-contacts-vcard-file'.  If TO-BUFFER is non-nil, the buffer
 
- is created and the VCard is written into that buffer."
 
-   (let* ((filename (or file org-contacts-vcard-file))
 
- 	 (buffer (if to-buffer
 
- 		     (get-buffer-create to-buffer)
 
- 		   (find-file-noselect filename))))
 
-     (message "Exporting...")
 
-     (set-buffer buffer)
 
-     (let ((inhibit-read-only t)) (erase-buffer))
 
-     (fundamental-mode)
 
-     (when (fboundp 'set-buffer-file-coding-system)
 
-       (set-buffer-file-coding-system coding-system-for-write))
 
-     (loop for contact in (org-contacts-filter name)
 
- 	  do (insert (org-contacts-vcard-format contact)))
 
-     (if to-buffer
 
- 	(current-buffer)
 
-       (progn (save-buffer) (kill-buffer)))))
 
- (defun org-contacts-show-map (&optional name)
 
-   "Show contacts on a map.
 
- Requires google-maps-el."
 
-   (interactive)
 
-   (unless (fboundp 'google-maps-static-show)
 
-     (error "`org-contacts-show-map' requires `google-maps-el'"))
 
-   (google-maps-static-show
 
-    :markers
 
-    (cl-loop
 
-     for contact in (org-contacts-filter name)
 
-     for addr = (cdr (assoc-string org-contacts-address-property (nth 2 contact)))
 
-     if addr
 
-     collect (cons (list addr) (list :label (string-to-char (car contact)))))))
 
- (defun org-contacts-strip-link (link)
 
-   "Remove brackets, description, link type and colon from an org
 
- link string and return the pure link target."
 
-    (let (startpos colonpos endpos)
 
-      (setq startpos (string-match (regexp-opt '("[[tel:" "[[mailto:")) link))
 
-      (if startpos
 
-          (progn
 
-             (setq colonpos (string-match ":" link))
 
-             (setq endpos (string-match "\\]" link))
 
-             (if endpos (substring link (1+ colonpos) endpos) link))
 
-          (progn
 
-             (setq startpos (string-match "mailto:" link))
 
-             (setq colonpos (string-match ":" link))
 
-             (if startpos (substring link (1+ colonpos)) link)))))
 
- ;; Add the link type supported by org-contacts-strip-link
 
- ;; so everything is in order for its use in Org files
 
- (org-link-set-parameters "tel")
 
- (defun org-contacts-split-property (string &optional separators omit-nulls)
 
-   "Custom version of `split-string'.
 
- Split a property STRING into sub-strings bounded by matches
 
- for SEPARATORS but keep Org links intact.
 
- The beginning and end of STRING, and each match for SEPARATORS, are
 
- splitting points.  The substrings matching SEPARATORS are removed, and
 
- the substrings between the splitting points are collected as a list,
 
- which is returned.
 
- If SEPARATORS is non-nil, it should be a regular expression
 
- matching text which separates, but is not part of, the
 
- substrings.  If nil it defaults to `org-contacts-property-values-separators',
 
- normally \"[,; \f\t\n\r\v]+\", and OMIT-NULLS is forced to t.
 
- If OMIT-NULLS is t, zero-length substrings are omitted from the list \(so
 
- that for the default value of SEPARATORS leading and trailing whitespace
 
- are effectively trimmed).  If nil, all zero-length substrings are retained."
 
-   (let* ((omit-nulls (if separators omit-nulls t))
 
- 	 (rexp (or separators org-contacts-property-values-separators))
 
- 	 (inputlist (split-string string rexp omit-nulls))
 
- 	 (linkstring "")
 
- 	 (bufferstring "")
 
- 	 (proplist (list "")))
 
-     (while inputlist
 
-       (setq bufferstring (pop inputlist))
 
-       (if (string-match "\\[\\[" bufferstring)
 
-           (progn
 
-             (setq linkstring (concat bufferstring " "))
 
-             (while (not (string-match "\\]\\]" bufferstring))
 
-               (setq bufferstring (pop inputlist))
 
-               (setq linkstring (concat  linkstring bufferstring " ")))
 
-             (setq proplist (cons (org-trim linkstring) proplist)))
 
-         (setq proplist (cons bufferstring proplist))))
 
-     (cdr (reverse proplist))))
 
- (provide 'org-contacts)
 
- ;;; org-contacts.el ends here
 
 
  |