eudc.el 43 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292
  1. ;;; eudc.el --- Emacs Unified Directory Client
  2. ;; Copyright (C) 1998-2017 Free Software Foundation, Inc.
  3. ;; Author: Oscar Figueiredo <oscar@cpe.fr>
  4. ;; Pavel Janík <Pavel@Janik.cz>
  5. ;; Maintainer: Thomas Fitzsimmons <fitzsim@fitzsim.org>
  6. ;; Keywords: comm
  7. ;; This file is part of GNU Emacs.
  8. ;; GNU Emacs is free software: you can redistribute it and/or modify
  9. ;; it under the terms of the GNU General Public License as published by
  10. ;; the Free Software Foundation, either version 3 of the License, or
  11. ;; (at your option) any later version.
  12. ;; GNU Emacs is distributed in the hope that it will be useful,
  13. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. ;; GNU General Public License for more details.
  16. ;; You should have received a copy of the GNU General Public License
  17. ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
  18. ;;; Commentary:
  19. ;; This package provides a common interface to query directory servers using
  20. ;; different protocols such as LDAP, CCSO PH/QI or BBDB. Queries can be
  21. ;; made through an interactive form or inline. Inline query strings in
  22. ;; buffers are expanded with appropriately formatted query results
  23. ;; (especially used to expand email addresses in message buffers). EUDC
  24. ;; also interfaces with the BBDB package to let you register query results
  25. ;; into your own BBDB database.
  26. ;;; Usage:
  27. ;; EUDC comes with an extensive documentation, please refer to it.
  28. ;;
  29. ;; The main entry points of EUDC are:
  30. ;; `eudc-query-form': Query a directory server from a query form
  31. ;; `eudc-expand-inline': Query a directory server for the e-mail address
  32. ;; of the name before cursor and insert it in the
  33. ;; buffer
  34. ;; `eudc-get-phone': Get a phone number from a directory server
  35. ;; `eudc-get-email': Get an e-mail address from a directory server
  36. ;; `eudc-customize': Customize various aspects of EUDC
  37. ;;; Code:
  38. (require 'wid-edit)
  39. (eval-when-compile (require 'cl-lib))
  40. (eval-and-compile
  41. (if (not (fboundp 'make-overlay))
  42. (require 'overlay)))
  43. (unless (fboundp 'custom-menu-create)
  44. (autoload 'custom-menu-create "cus-edit"))
  45. (require 'eudc-vars)
  46. ;;{{{ Internal cooking
  47. ;;{{{ Internal variables and compatibility tricks
  48. (defvar eudc-form-widget-list nil)
  49. (defvar eudc-mode-map
  50. (let ((map (make-sparse-keymap)))
  51. (define-key map "q" 'kill-current-buffer)
  52. (define-key map "x" 'kill-current-buffer)
  53. (define-key map "f" 'eudc-query-form)
  54. (define-key map "b" 'eudc-try-bbdb-insert)
  55. (define-key map "n" 'eudc-move-to-next-record)
  56. (define-key map "p" 'eudc-move-to-previous-record)
  57. map))
  58. (set-keymap-parent eudc-mode-map widget-keymap)
  59. (defvar mode-popup-menu)
  60. ;; List of variables that have server- or protocol-local bindings
  61. (defvar eudc-local-vars nil)
  62. ;; Protocol local. Query function
  63. (defvar eudc-query-function nil)
  64. ;; Protocol local. A function that retrieves a list of valid attribute names
  65. (defvar eudc-list-attributes-function nil)
  66. ;; Protocol local. A mapping between EUDC attribute names and corresponding
  67. ;; protocol specific names. The following names are defined by EUDC and may be
  68. ;; included in that list: `name' , `firstname', `email', `phone'
  69. (defvar eudc-protocol-attributes-translation-alist nil)
  70. ;; Protocol local. Mapping between protocol attribute names and BBDB field
  71. ;; names
  72. (defvar eudc-bbdb-conversion-alist nil)
  73. ;; Protocol/Server local. Hook called upon switching to that server
  74. (defvar eudc-switch-to-server-hook nil)
  75. ;; Protocol/Server local. Hook called upon switching from that server
  76. (defvar eudc-switch-from-server-hook nil)
  77. ;; Protocol local. Whether the protocol supports queries with no specified
  78. ;; attribute name
  79. (defvar eudc-protocol-has-default-query-attributes nil)
  80. (defvar bbdb-version)
  81. (defun eudc--using-bbdb-3-or-newer-p ()
  82. "Return non-nil if BBDB version is 3 or greater."
  83. (or
  84. ;; MELPA versions of BBDB may have a bad package version, but
  85. ;; they're all version 3 or later.
  86. (equal bbdb-version "@PACKAGE_VERSION@")
  87. ;; Development versions of BBDB can have the format "X.YZ devo".
  88. ;; Split the string just in case.
  89. (version<= "3" (car (split-string bbdb-version)))))
  90. (defun eudc-plist-member (plist prop)
  91. "Return t if PROP has a value specified in PLIST."
  92. (if (not (= 0 (% (length plist) 2)))
  93. (error "Malformed plist"))
  94. (catch 'found
  95. (while plist
  96. (if (eq prop (car plist))
  97. (throw 'found t))
  98. (setq plist (cdr (cdr plist))))
  99. nil))
  100. ;; Emacs's plist-get lacks third parameter
  101. (defun eudc-plist-get (plist prop &optional default)
  102. "Extract a value from a property list.
  103. PLIST is a property list, which is a list of the form
  104. \(PROP1 VALUE1 PROP2 VALUE2...). This function returns the value
  105. corresponding to the given PROP, or DEFAULT if PROP is not
  106. one of the properties on the list."
  107. (if (eudc-plist-member plist prop)
  108. (plist-get plist prop)
  109. default))
  110. (defun eudc-lax-plist-get (plist prop &optional default)
  111. "Extract a value from a lax property list.
  112. PLIST is a lax property list, which is a list of the form (PROP1
  113. VALUE1 PROP2 VALUE2...), where comparisons between properties are done
  114. using `equal' instead of `eq'. This function returns the value
  115. corresponding to PROP, or DEFAULT if PROP is not one of the
  116. properties on the list."
  117. (if (not (= 0 (% (length plist) 2)))
  118. (error "Malformed plist"))
  119. (catch 'found
  120. (while plist
  121. (if (equal prop (car plist))
  122. (throw 'found (car (cdr plist))))
  123. (setq plist (cdr (cdr plist))))
  124. default))
  125. (if (not (fboundp 'split-string))
  126. (defun split-string (string &optional pattern)
  127. "Return a list of substrings of STRING which are separated by PATTERN.
  128. If PATTERN is omitted, it defaults to \"[ \\f\\t\\n\\r\\v]+\"."
  129. (or pattern
  130. (setq pattern "[ \f\t\n\r\v]+"))
  131. (let (parts (start 0))
  132. (when (string-match pattern string 0)
  133. (if (> (match-beginning 0) 0)
  134. (setq parts (cons (substring string 0 (match-beginning 0)) nil)))
  135. (setq start (match-end 0))
  136. (while (and (string-match pattern string start)
  137. (> (match-end 0) start))
  138. (setq parts (cons (substring string start (match-beginning 0)) parts)
  139. start (match-end 0))))
  140. (nreverse (if (< start (length string))
  141. (cons (substring string start) parts)
  142. parts)))))
  143. (defun eudc-replace-in-string (str regexp newtext)
  144. "Replace all matches in STR for REGEXP with NEWTEXT.
  145. Value is the new string."
  146. (let ((rtn-str "")
  147. (start 0)
  148. match prev-start)
  149. (while (setq match (string-match regexp str start))
  150. (setq prev-start start
  151. start (match-end 0)
  152. rtn-str
  153. (concat rtn-str
  154. (substring str prev-start match)
  155. newtext)))
  156. (concat rtn-str (substring str start))))
  157. ;;}}}
  158. ;;{{{ Server and Protocol Variable Routines
  159. (defun eudc-server-local-variable-p (var)
  160. "Return non-nil if VAR has server-local bindings."
  161. (eudc-plist-member (get var 'eudc-locals) 'server))
  162. (defun eudc-protocol-local-variable-p (var)
  163. "Return non-nil if VAR has protocol-local bindings."
  164. (eudc-plist-member (get var 'eudc-locals) 'protocol))
  165. (defun eudc-default-set (var val)
  166. "Set the EUDC default value of VAR to VAL.
  167. The current binding of VAR is not changed."
  168. (put var 'eudc-locals
  169. (plist-put (get var 'eudc-locals) 'default val))
  170. (add-to-list 'eudc-local-vars var))
  171. (defun eudc-protocol-set (var val &optional protocol)
  172. "Set the PROTOCOL-local binding of VAR to VAL.
  173. If omitted PROTOCOL defaults to the current value of `eudc-protocol'.
  174. The current binding of VAR is changed only if PROTOCOL is omitted."
  175. (if (eq 'unbound (eudc-variable-default-value var))
  176. (eudc-default-set var (symbol-value var)))
  177. (let* ((eudc-locals (get var 'eudc-locals))
  178. (protocol-locals (eudc-plist-get eudc-locals 'protocol)))
  179. (setq protocol-locals (plist-put protocol-locals (or protocol
  180. eudc-protocol) val))
  181. (setq eudc-locals
  182. (plist-put eudc-locals 'protocol protocol-locals))
  183. (put var 'eudc-locals eudc-locals)
  184. (add-to-list 'eudc-local-vars var)
  185. (unless protocol
  186. (eudc-update-variable var))))
  187. (defun eudc-server-set (var val &optional server)
  188. "Set the SERVER-local binding of VAR to VAL.
  189. If omitted SERVER defaults to the current value of `eudc-server'.
  190. The current binding of VAR is changed only if SERVER is omitted."
  191. (if (eq 'unbound (eudc-variable-default-value var))
  192. (eudc-default-set var (symbol-value var)))
  193. (let* ((eudc-locals (get var 'eudc-locals))
  194. (server-locals (eudc-plist-get eudc-locals 'server)))
  195. (setq server-locals (plist-put server-locals (or server
  196. eudc-server) val))
  197. (setq eudc-locals
  198. (plist-put eudc-locals 'server server-locals))
  199. (put var 'eudc-locals eudc-locals)
  200. (add-to-list 'eudc-local-vars var)
  201. (unless server
  202. (eudc-update-variable var))))
  203. (defun eudc-set (var val)
  204. "Set the most local (server, protocol or default) binding of VAR to VAL.
  205. The current binding of VAR is also set to VAL"
  206. (cond
  207. ((not (eq 'unbound (eudc-variable-server-value var)))
  208. (eudc-server-set var val))
  209. ((not (eq 'unbound (eudc-variable-protocol-value var)))
  210. (eudc-protocol-set var val))
  211. (t
  212. (eudc-default-set var val)))
  213. (set var val))
  214. (defun eudc-variable-default-value (var)
  215. "Return the default binding of VAR.
  216. Return `unbound' if VAR has no EUDC default value."
  217. (let ((eudc-locals (get var 'eudc-locals)))
  218. (if (and (boundp var)
  219. eudc-locals)
  220. (eudc-plist-get eudc-locals 'default 'unbound)
  221. 'unbound)))
  222. (defun eudc-variable-protocol-value (var &optional protocol)
  223. "Return the value of VAR local to PROTOCOL.
  224. Return `unbound' if VAR has no value local to PROTOCOL.
  225. PROTOCOL defaults to `eudc-protocol'"
  226. (let* ((eudc-locals (get var 'eudc-locals))
  227. protocol-locals)
  228. (if (not (and (boundp var)
  229. eudc-locals
  230. (eudc-plist-member eudc-locals 'protocol)))
  231. 'unbound
  232. (setq protocol-locals (eudc-plist-get eudc-locals 'protocol))
  233. (eudc-lax-plist-get protocol-locals
  234. (or protocol
  235. eudc-protocol) 'unbound))))
  236. (defun eudc-variable-server-value (var &optional server)
  237. "Return the value of VAR local to SERVER.
  238. Return `unbound' if VAR has no value local to SERVER.
  239. SERVER defaults to `eudc-server'"
  240. (let* ((eudc-locals (get var 'eudc-locals))
  241. server-locals)
  242. (if (not (and (boundp var)
  243. eudc-locals
  244. (eudc-plist-member eudc-locals 'server)))
  245. 'unbound
  246. (setq server-locals (eudc-plist-get eudc-locals 'server))
  247. (eudc-lax-plist-get server-locals
  248. (or server
  249. eudc-server) 'unbound))))
  250. (defun eudc-update-variable (var)
  251. "Set the value of VAR according to its locals.
  252. If the VAR has a server- or protocol-local value corresponding
  253. to the current `eudc-server' and `eudc-protocol' then it is set
  254. accordingly. Otherwise it is set to its EUDC default binding"
  255. (let (val)
  256. (cond
  257. ((not (eq 'unbound (setq val (eudc-variable-server-value var))))
  258. (set var val))
  259. ((not (eq 'unbound (setq val (eudc-variable-protocol-value var))))
  260. (set var val))
  261. ((not (eq 'unbound (setq val (eudc-variable-default-value var))))
  262. (set var val)))))
  263. (defun eudc-update-local-variables ()
  264. "Update all EUDC variables according to their local settings."
  265. (interactive)
  266. (mapcar 'eudc-update-variable eudc-local-vars))
  267. (eudc-default-set 'eudc-query-function nil)
  268. (eudc-default-set 'eudc-list-attributes-function nil)
  269. (eudc-default-set 'eudc-protocol-attributes-translation-alist nil)
  270. (eudc-default-set 'eudc-bbdb-conversion-alist nil)
  271. (eudc-default-set 'eudc-switch-to-server-hook nil)
  272. (eudc-default-set 'eudc-switch-from-server-hook nil)
  273. (eudc-default-set 'eudc-protocol-has-default-query-attributes nil)
  274. (eudc-default-set 'eudc-attribute-display-method-alist nil)
  275. ;;}}}
  276. ;; Add PROTOCOL to the list of supported protocols
  277. (defun eudc-register-protocol (protocol)
  278. (unless (memq protocol eudc-supported-protocols)
  279. (setq eudc-supported-protocols
  280. (cons protocol eudc-supported-protocols))
  281. (put 'eudc-protocol 'custom-type
  282. `(choice :menu-tag "Protocol"
  283. ,@(mapcar (lambda (s)
  284. (list 'string ':tag (symbol-name s)))
  285. eudc-supported-protocols))))
  286. (or (memq protocol eudc-known-protocols)
  287. (setq eudc-known-protocols
  288. (cons protocol eudc-known-protocols))))
  289. (defun eudc-translate-query (query)
  290. "Translate attribute names of QUERY.
  291. The translation is done according to
  292. `eudc-protocol-attributes-translation-alist'."
  293. (if eudc-protocol-attributes-translation-alist
  294. (mapcar (lambda (attribute)
  295. (let ((trans (assq (car attribute)
  296. (symbol-value eudc-protocol-attributes-translation-alist))))
  297. (if trans
  298. (cons (cdr trans) (cdr attribute))
  299. attribute)))
  300. query)
  301. query))
  302. (defun eudc-translate-attribute-list (list)
  303. "Translate a list of attribute names LIST.
  304. The translation is done according to
  305. `eudc-protocol-attributes-translation-alist'."
  306. (if eudc-protocol-attributes-translation-alist
  307. (let (trans)
  308. (mapcar (lambda (attribute)
  309. (setq trans (assq attribute
  310. (symbol-value eudc-protocol-attributes-translation-alist)))
  311. (if trans
  312. (cdr trans)
  313. attribute))
  314. list))
  315. list))
  316. (defun eudc-select (choices beg end)
  317. "Choose one from CHOICES using a completion.
  318. BEG and END delimit the text which is to be replaced."
  319. (let ((replacement))
  320. (setq replacement
  321. (completing-read "Multiple matches found; choose one: "
  322. (mapcar 'list choices)))
  323. (delete-region beg end)
  324. (insert replacement)))
  325. (defun eudc-query (query &optional return-attributes no-translation)
  326. "Query the current directory server with QUERY.
  327. QUERY is a list of cons cells (ATTR . VALUE) where ATTR is an attribute
  328. name and VALUE the corresponding value.
  329. If NO-TRANSLATION is non-nil, ATTR is translated according to
  330. `eudc-protocol-attributes-translation-alist'.
  331. RETURN-ATTRIBUTES is a list of attributes to return defaulting to
  332. `eudc-default-return-attributes'."
  333. (unless eudc-query-function
  334. (error "Don't know how to perform the query"))
  335. (if no-translation
  336. (funcall eudc-query-function query (or return-attributes
  337. eudc-default-return-attributes))
  338. (funcall eudc-query-function
  339. (eudc-translate-query query)
  340. (cond
  341. (return-attributes
  342. (eudc-translate-attribute-list return-attributes))
  343. ((listp eudc-default-return-attributes)
  344. (eudc-translate-attribute-list eudc-default-return-attributes))
  345. (t
  346. eudc-default-return-attributes)))))
  347. (defun eudc-format-attribute-name-for-display (attribute)
  348. "Format a directory attribute name for display.
  349. ATTRIBUTE is looked up in `eudc-user-attribute-names-alist' and replaced
  350. by the corresponding user name if any. Otherwise it is capitalized and
  351. underscore characters are replaced by spaces."
  352. (let ((match (assq attribute eudc-user-attribute-names-alist)))
  353. (if match
  354. (cdr match)
  355. (capitalize
  356. (mapconcat 'identity
  357. (split-string (symbol-name attribute) "_")
  358. " ")))))
  359. (defun eudc-print-attribute-value (field)
  360. "Insert the value of the directory FIELD at point.
  361. The directory attribute name in car of FIELD is looked up in
  362. `eudc-attribute-display-method-alist' and the corresponding method,
  363. if any, is called to print the value in cdr of FIELD."
  364. (let ((match (assoc (downcase (car field))
  365. eudc-attribute-display-method-alist))
  366. (col (current-column))
  367. (val (cdr field)))
  368. (if match
  369. (progn
  370. (eval (list (cdr match) val))
  371. (insert "\n"))
  372. (mapcar
  373. (function
  374. (lambda (val-elem)
  375. (indent-to col)
  376. (insert val-elem "\n")))
  377. (cond
  378. ((listp val) val)
  379. ((stringp val) (split-string val "\n"))
  380. ((null val) '(""))
  381. (t (list val)))))))
  382. (defun eudc-print-record-field (field column-width)
  383. "Print the record field FIELD.
  384. FIELD is a list (ATTR VALUE1 VALUE2 ...) or cons-cell (ATTR . VAL)
  385. COLUMN-WIDTH is the width of the first display column containing the
  386. attribute name ATTR."
  387. (let ((field-beg (point)))
  388. ;; The record field that is passed to this function has already been processed
  389. ;; by `eudc-format-attribute-name-for-display' so we don't need to call it
  390. ;; again to display the attribute name
  391. (insert (format (concat "%" (int-to-string column-width) "s: ")
  392. (car field)))
  393. (put-text-property field-beg (point) 'face 'bold)
  394. (indent-to (+ 2 column-width))
  395. (eudc-print-attribute-value field)))
  396. (defun eudc-display-records (records &optional raw-attr-names)
  397. "Display the record list RECORDS in a formatted buffer.
  398. If RAW-ATTR-NAMES is non-nil, the raw attribute names are displayed
  399. otherwise they are formatted according to `eudc-user-attribute-names-alist'."
  400. (let (inhibit-read-only
  401. precords
  402. (width 0)
  403. beg
  404. first-record
  405. attribute-name)
  406. (with-output-to-temp-buffer "*Directory Query Results*"
  407. (with-current-buffer standard-output
  408. (setq buffer-read-only t)
  409. (setq inhibit-read-only t)
  410. (erase-buffer)
  411. (insert "Directory Query Result\n")
  412. (insert "======================\n\n\n")
  413. (if (null records)
  414. (insert "No match found.\n"
  415. (if eudc-strict-return-matches
  416. "Try setting `eudc-strict-return-matches' to nil or change `eudc-default-return-attributes'.\n"
  417. ""))
  418. ;; Replace field names with user names, compute max width
  419. (setq precords
  420. (mapcar
  421. (function
  422. (lambda (record)
  423. (mapcar
  424. (function
  425. (lambda (field)
  426. (setq attribute-name
  427. (if raw-attr-names
  428. (symbol-name (car field))
  429. (eudc-format-attribute-name-for-display (car field))))
  430. (if (> (length attribute-name) width)
  431. (setq width (length attribute-name)))
  432. (cons attribute-name (cdr field))))
  433. record)))
  434. records))
  435. ;; Display the records
  436. (setq first-record (point))
  437. (mapc
  438. (function
  439. (lambda (record)
  440. (setq beg (point))
  441. ;; Map over the record fields to print the attribute/value pairs
  442. (mapc (function
  443. (lambda (field)
  444. (eudc-print-record-field field width)))
  445. record)
  446. ;; Store the record internal format in some convenient place
  447. (overlay-put (make-overlay beg (point))
  448. 'eudc-record
  449. (car records))
  450. (setq records (cdr records))
  451. (insert "\n")))
  452. precords))
  453. (insert "\n")
  454. (widget-create 'push-button
  455. :notify (lambda (&rest _ignore)
  456. (eudc-query-form))
  457. "New query")
  458. (widget-insert " ")
  459. (widget-create 'push-button
  460. :notify (lambda (&rest _ignore)
  461. (kill-this-buffer))
  462. "Quit")
  463. (eudc-mode)
  464. (widget-setup)
  465. (if first-record
  466. (goto-char first-record))))))
  467. (defun eudc-process-form ()
  468. "Process the query form in current buffer and display the results."
  469. (let (query-alist
  470. value)
  471. (if (not (and (boundp 'eudc-form-widget-list)
  472. eudc-form-widget-list))
  473. (error "Not in a directory query form buffer")
  474. (mapc (function
  475. (lambda (wid-field)
  476. (setq value (widget-value (cdr wid-field)))
  477. (if (not (string= value ""))
  478. (setq query-alist (cons (cons (car wid-field) value)
  479. query-alist)))))
  480. eudc-form-widget-list)
  481. (kill-buffer (current-buffer))
  482. (eudc-display-records (eudc-query query-alist) eudc-use-raw-directory-names))))
  483. (defun eudc-filter-duplicate-attributes (record)
  484. "Filter RECORD according to `eudc-duplicate-attribute-handling-method'."
  485. (let ((rec record)
  486. unique
  487. duplicates
  488. result)
  489. ;; Search for multiple records
  490. (while (and rec
  491. (not (listp (cdar rec))))
  492. (setq rec (cdr rec)))
  493. (if (null (cdar rec))
  494. (list record) ; No duplicate attrs in this record
  495. (mapc (function
  496. (lambda (field)
  497. (if (listp (cdr field))
  498. (setq duplicates (cons field duplicates))
  499. (setq unique (cons field unique)))))
  500. record)
  501. (setq result (list unique))
  502. ;; Map over the record fields that have multiple values
  503. (mapc
  504. (function
  505. (lambda (field)
  506. (let ((method (if (consp eudc-duplicate-attribute-handling-method)
  507. (cdr
  508. (assq
  509. (or
  510. (car
  511. (rassq
  512. (car field)
  513. (symbol-value
  514. eudc-protocol-attributes-translation-alist)))
  515. (car field))
  516. eudc-duplicate-attribute-handling-method))
  517. eudc-duplicate-attribute-handling-method)))
  518. (cond
  519. ((or (null method) (eq 'list method))
  520. (setq result
  521. (eudc-add-field-to-records field result)))
  522. ((eq 'first method)
  523. (setq result
  524. (eudc-add-field-to-records (cons (car field)
  525. (cadr field))
  526. result)))
  527. ((eq 'concat method)
  528. (setq result
  529. (eudc-add-field-to-records (cons (car field)
  530. (mapconcat
  531. 'identity
  532. (cdr field)
  533. "\n")) result)))
  534. ((eq 'duplicate method)
  535. (setq result
  536. (eudc-distribute-field-on-records field result)))))))
  537. duplicates)
  538. result)))
  539. (defun eudc-filter-partial-records (records attrs)
  540. "Eliminate records that do not contain all ATTRS from RECORDS."
  541. (delq nil
  542. (mapcar
  543. (function
  544. (lambda (rec)
  545. (if (eval (cons 'and
  546. (mapcar
  547. (function
  548. (lambda (attr)
  549. (consp (assq attr rec))))
  550. attrs)))
  551. rec)))
  552. records)))
  553. (defun eudc-add-field-to-records (field records)
  554. "Add FIELD to each individual record in RECORDS and return the resulting list."
  555. (mapcar (function
  556. (lambda (r)
  557. (cons field r)))
  558. records))
  559. (defun eudc-distribute-field-on-records (field records)
  560. "Duplicate each individual record in RECORDS according to value of FIELD.
  561. Each copy is added a new field containing one of the values of FIELD."
  562. (let (result
  563. (values (cdr field)))
  564. ;; Uniquify values first
  565. (while values
  566. (setcdr values (delete (car values) (cdr values)))
  567. (setq values (cdr values)))
  568. (mapc
  569. (function
  570. (lambda (value)
  571. (let ((result-list (copy-sequence records)))
  572. (setq result-list (eudc-add-field-to-records
  573. (cons (car field) value)
  574. result-list))
  575. (setq result (append result-list result))
  576. )))
  577. (cdr field))
  578. result))
  579. (define-derived-mode eudc-mode special-mode "EUDC"
  580. "Major mode used in buffers displaying the results of directory queries.
  581. There is no sense in calling this command from a buffer other than
  582. one containing the results of a directory query.
  583. These are the special commands of EUDC mode:
  584. q -- Kill this buffer.
  585. f -- Display a form to query the current directory server.
  586. n -- Move to next record.
  587. p -- Move to previous record.
  588. b -- Insert record at point into the BBDB database."
  589. (if (not (featurep 'xemacs))
  590. (easy-menu-define eudc-emacs-menu eudc-mode-map "" (eudc-menu))
  591. (setq mode-popup-menu (eudc-menu))))
  592. ;;}}}
  593. ;;{{{ High-level interfaces (interactive functions)
  594. (defun eudc-customize ()
  595. "Customize the EUDC package."
  596. (interactive)
  597. (customize-group 'eudc))
  598. ;;;###autoload
  599. (defun eudc-set-server (server protocol &optional no-save)
  600. "Set the directory server to SERVER using PROTOCOL.
  601. Unless NO-SAVE is non-nil, the server is saved as the default
  602. server for future sessions."
  603. (interactive (list
  604. (read-from-minibuffer "Directory Server: ")
  605. (intern (completing-read "Protocol: "
  606. (mapcar (lambda (elt)
  607. (cons (symbol-name elt)
  608. elt))
  609. eudc-known-protocols)))))
  610. (unless (or (null protocol)
  611. (member protocol
  612. eudc-supported-protocols)
  613. (load (concat "eudcb-" (symbol-name protocol)) t))
  614. (error "Unsupported protocol: %s" protocol))
  615. (run-hooks 'eudc-switch-from-server-hook)
  616. (setq eudc-protocol protocol)
  617. (setq eudc-server server)
  618. (eudc-update-local-variables)
  619. (run-hooks 'eudc-switch-to-server-hook)
  620. (if (called-interactively-p 'interactive)
  621. (message "Current directory server is now %s (%s)" eudc-server eudc-protocol))
  622. (if (null no-save)
  623. (eudc-save-options)))
  624. ;;;###autoload
  625. (defun eudc-get-email (name &optional error)
  626. "Get the email field of NAME from the directory server.
  627. If ERROR is non-nil, report an error if there is none."
  628. (interactive "sName: \np")
  629. (or eudc-server
  630. (call-interactively 'eudc-set-server))
  631. (let ((result (eudc-query (list (cons 'name name)) '(email)))
  632. email)
  633. (if (null (cdr result))
  634. (setq email (cl-cdaar result))
  635. (error "Multiple match--use the query form"))
  636. (if error
  637. (if email
  638. (message "%s" email)
  639. (error "No record matching %s" name)))
  640. email))
  641. ;;;###autoload
  642. (defun eudc-get-phone (name &optional error)
  643. "Get the phone field of NAME from the directory server.
  644. If ERROR is non-nil, report an error if there is none."
  645. (interactive "sName: \np")
  646. (or eudc-server
  647. (call-interactively 'eudc-set-server))
  648. (let ((result (eudc-query (list (cons 'name name)) '(phone)))
  649. phone)
  650. (if (null (cdr result))
  651. (setq phone (cl-cdaar result))
  652. (error "Multiple match--use the query form"))
  653. (if error
  654. (if phone
  655. (message "%s" phone)
  656. (error "No record matching %s" name)))
  657. phone))
  658. (defun eudc-get-attribute-list ()
  659. "Return a list of valid attributes for the current server.
  660. When called interactively the list is formatted in a dedicated buffer
  661. otherwise a list of symbols is returned."
  662. (interactive)
  663. (if eudc-list-attributes-function
  664. (let ((entries (funcall eudc-list-attributes-function
  665. (called-interactively-p 'interactive))))
  666. (if entries
  667. (if (called-interactively-p 'interactive)
  668. (eudc-display-records entries t)
  669. entries)))
  670. (error "The %s protocol has no support for listing attributes" eudc-protocol)))
  671. (defun eudc-format-query (words format)
  672. "Use FORMAT to build a EUDC query from WORDS."
  673. (let (query
  674. query-alist
  675. key val cell)
  676. (if format
  677. (progn
  678. (while (and words format)
  679. (setq query-alist (cons (cons (car format) (car words))
  680. query-alist))
  681. (setq words (cdr words)
  682. format (cdr format)))
  683. ;; If the same attribute appears more than once, merge
  684. ;; the corresponding values
  685. (while query-alist
  686. (setq key (caar query-alist)
  687. val (cdar query-alist)
  688. cell (assq key query))
  689. (if cell
  690. (setcdr cell (concat (cdr cell) " " val))
  691. (setq query (cons (car query-alist) query)))
  692. (setq query-alist (cdr query-alist)))
  693. query)
  694. (if eudc-protocol-has-default-query-attributes
  695. (mapconcat 'identity words " ")
  696. (list (cons 'name (mapconcat 'identity words " ")))))))
  697. (defun eudc-extract-n-word-formats (format-list n)
  698. "Extract a list of N-long formats from FORMAT-LIST.
  699. If none try N - 1 and so forth."
  700. (let (formats)
  701. (while (and (null formats)
  702. (> n 0))
  703. (setq formats
  704. (delq nil
  705. (mapcar (lambda (format)
  706. (if (= n
  707. (length format))
  708. format
  709. nil))
  710. format-list)))
  711. (setq n (1- n)))
  712. formats))
  713. ;;;###autoload
  714. (defun eudc-expand-inline (&optional replace)
  715. "Query the directory server, and expand the query string before point.
  716. The query string consists of the buffer substring from the point back to
  717. the preceding comma, colon or beginning of line.
  718. The variable `eudc-inline-query-format' controls how to associate the
  719. individual inline query words with directory attribute names.
  720. After querying the server for the given string, the expansion specified by
  721. `eudc-inline-expansion-format' is inserted in the buffer at point.
  722. If REPLACE is non-nil, then this expansion replaces the name in the buffer.
  723. `eudc-expansion-overwrites-query' being non-nil inverts the meaning of REPLACE.
  724. Multiple servers can be tried with the same query until one finds a match,
  725. see `eudc-inline-expansion-servers'"
  726. (interactive)
  727. (cond
  728. ((eq eudc-inline-expansion-servers 'current-server)
  729. (or eudc-server
  730. (call-interactively 'eudc-set-server)))
  731. ((eq eudc-inline-expansion-servers 'server-then-hotlist)
  732. (or eudc-server
  733. ;; Allow server to be nil if hotlist is set.
  734. eudc-server-hotlist
  735. (call-interactively 'eudc-set-server)))
  736. ((eq eudc-inline-expansion-servers 'hotlist)
  737. (or eudc-server-hotlist
  738. (error "No server in the hotlist")))
  739. (t
  740. (error "Wrong value for `eudc-inline-expansion-servers': %S"
  741. eudc-inline-expansion-servers)))
  742. (let* ((end (point))
  743. (beg (save-excursion
  744. (if (re-search-backward "\\([:,]\\|^\\)[ \t]*"
  745. (point-at-bol) 'move)
  746. (goto-char (match-end 0)))
  747. (point)))
  748. (query-words (split-string (buffer-substring-no-properties beg end)
  749. "[ \t]+"))
  750. query-formats
  751. response
  752. response-string
  753. response-strings
  754. (eudc-former-server eudc-server)
  755. (eudc-former-protocol eudc-protocol)
  756. servers)
  757. ;; Prepare the list of servers to query
  758. (setq servers (copy-sequence eudc-server-hotlist))
  759. (setq servers
  760. (cond
  761. ((eq eudc-inline-expansion-servers 'hotlist)
  762. eudc-server-hotlist)
  763. ((eq eudc-inline-expansion-servers 'server-then-hotlist)
  764. (if eudc-server
  765. (cons (cons eudc-server eudc-protocol)
  766. (delete (cons eudc-server eudc-protocol) servers))
  767. eudc-server-hotlist))
  768. ((eq eudc-inline-expansion-servers 'current-server)
  769. (list (cons eudc-server eudc-protocol)))))
  770. (if (and eudc-max-servers-to-query
  771. (> (length servers) eudc-max-servers-to-query))
  772. (setcdr (nthcdr (1- eudc-max-servers-to-query) servers) nil))
  773. (unwind-protect
  774. (progn
  775. (setq response
  776. (catch 'found
  777. ;; Loop on the servers
  778. (while servers
  779. (eudc-set-server (caar servers) (cdar servers) t)
  780. ;; Determine which formats apply in the query-format list
  781. (setq query-formats
  782. (or
  783. (eudc-extract-n-word-formats eudc-inline-query-format
  784. (length query-words))
  785. (if (null eudc-protocol-has-default-query-attributes)
  786. '(name))))
  787. ;; Loop on query-formats
  788. (while query-formats
  789. (setq response
  790. (eudc-query
  791. (eudc-format-query query-words (car query-formats))
  792. (eudc-translate-attribute-list
  793. (cdr eudc-inline-expansion-format))))
  794. (if response
  795. (throw 'found response))
  796. (setq query-formats (cdr query-formats)))
  797. (setq servers (cdr servers)))
  798. ;; No more servers to try... no match found
  799. nil))
  800. (if (null response)
  801. (error "No match")
  802. ;; Process response through eudc-inline-expansion-format
  803. (while response
  804. (setq response-string
  805. (apply 'format
  806. (car eudc-inline-expansion-format)
  807. (mapcar (function
  808. (lambda (field)
  809. (or (cdr (assq field (car response)))
  810. "")))
  811. (eudc-translate-attribute-list
  812. (cdr eudc-inline-expansion-format)))))
  813. (if (> (length response-string) 0)
  814. (setq response-strings
  815. (cons response-string response-strings)))
  816. (setq response (cdr response)))
  817. (if (or
  818. (and replace (not eudc-expansion-overwrites-query))
  819. (and (not replace) eudc-expansion-overwrites-query))
  820. (kill-ring-save beg end))
  821. (cond
  822. ((or (= (length response-strings) 1)
  823. (null eudc-multiple-match-handling-method)
  824. (eq eudc-multiple-match-handling-method 'first))
  825. (delete-region beg end)
  826. (insert (car response-strings)))
  827. ((eq eudc-multiple-match-handling-method 'select)
  828. (eudc-select response-strings beg end))
  829. ((eq eudc-multiple-match-handling-method 'all)
  830. (delete-region beg end)
  831. (insert (mapconcat 'identity response-strings ", ")))
  832. ((eq eudc-multiple-match-handling-method 'abort)
  833. (error "There is more than one match for the query")))))
  834. (or (and (equal eudc-server eudc-former-server)
  835. (equal eudc-protocol eudc-former-protocol))
  836. (eudc-set-server eudc-former-server eudc-former-protocol t)))))
  837. ;;;###autoload
  838. (defun eudc-query-form (&optional get-fields-from-server)
  839. "Display a form to query the directory server.
  840. If given a non-nil argument GET-FIELDS-FROM-SERVER, the function first
  841. queries the server for the existing fields and displays a corresponding form."
  842. (interactive "P")
  843. (let ((fields (or (and get-fields-from-server
  844. (eudc-get-attribute-list))
  845. eudc-query-form-attributes))
  846. (buffer (get-buffer-create "*Directory Query Form*"))
  847. prompts
  848. widget
  849. (width 0)
  850. inhibit-read-only
  851. pt)
  852. (switch-to-buffer buffer)
  853. (setq inhibit-read-only t)
  854. (erase-buffer)
  855. (kill-all-local-variables)
  856. (make-local-variable 'eudc-form-widget-list)
  857. (widget-insert "Directory Query Form\n")
  858. (widget-insert "====================\n\n")
  859. (widget-insert "Current server is: " (or eudc-server
  860. (progn
  861. (call-interactively 'eudc-set-server)
  862. eudc-server))
  863. "\n")
  864. (widget-insert "Protocol : " (symbol-name eudc-protocol) "\n")
  865. ;; Build the list of prompts
  866. (setq prompts (if eudc-use-raw-directory-names
  867. (mapcar 'symbol-name (eudc-translate-attribute-list fields))
  868. (mapcar (function
  869. (lambda (field)
  870. (or (and (assq field eudc-user-attribute-names-alist)
  871. (cdr (assq field eudc-user-attribute-names-alist)))
  872. (capitalize (symbol-name field)))))
  873. fields)))
  874. ;; Loop over prompt strings to find the longest one
  875. (mapc (function
  876. (lambda (prompt)
  877. (if (> (length prompt) width)
  878. (setq width (length prompt)))))
  879. prompts)
  880. ;; Insert the first widget out of the mapcar to leave the cursor
  881. ;; in the first field
  882. (widget-insert "\n\n" (format (concat "%" (int-to-string width) "s: ") (car prompts)))
  883. (setq pt (point))
  884. (setq widget (widget-create 'editable-field :size 15))
  885. (setq eudc-form-widget-list (cons (cons (car fields) widget)
  886. eudc-form-widget-list))
  887. (setq fields (cdr fields))
  888. (setq prompts (cdr prompts))
  889. (mapc (function
  890. (lambda (field)
  891. (widget-insert "\n\n" (format (concat "%" (int-to-string width) "s: ") (car prompts)))
  892. (setq widget (widget-create 'editable-field
  893. :size 15))
  894. (setq eudc-form-widget-list (cons (cons field widget)
  895. eudc-form-widget-list))
  896. (setq prompts (cdr prompts))))
  897. fields)
  898. (widget-insert "\n\n")
  899. (widget-create 'push-button
  900. :notify (lambda (&rest _ignore)
  901. (eudc-process-form))
  902. "Query Server")
  903. (widget-insert " ")
  904. (widget-create 'push-button
  905. :notify (lambda (&rest _ignore)
  906. (eudc-query-form))
  907. "Reset Form")
  908. (widget-insert " ")
  909. (widget-create 'push-button
  910. :notify (lambda (&rest _ignore)
  911. (kill-this-buffer))
  912. "Quit")
  913. (goto-char pt)
  914. (use-local-map widget-keymap)
  915. (widget-setup))
  916. )
  917. (defun eudc-bookmark-server (server protocol)
  918. "Add SERVER using PROTOCOL to the EUDC `servers' hotlist."
  919. (interactive "sDirectory server: \nsProtocol: ")
  920. (if (member (cons server protocol) eudc-server-hotlist)
  921. (error "%s:%s is already in the hotlist" protocol server)
  922. (setq eudc-server-hotlist (cons (cons server protocol) eudc-server-hotlist))
  923. (eudc-install-menu)
  924. (eudc-save-options)))
  925. (defun eudc-bookmark-current-server ()
  926. "Add current server to the EUDC `servers' hotlist."
  927. (interactive)
  928. (eudc-bookmark-server eudc-server eudc-protocol))
  929. (defun eudc-save-options ()
  930. "Save options to `eudc-options-file'."
  931. (interactive)
  932. (with-current-buffer (find-file-noselect eudc-options-file t)
  933. (goto-char (point-min))
  934. ;; delete the previous setq
  935. (let ((standard-output (current-buffer))
  936. provide-p
  937. set-hotlist-p
  938. set-server-p)
  939. (catch 'found
  940. (while t
  941. (let ((sexp (condition-case nil
  942. (read (current-buffer))
  943. (end-of-file (throw 'found nil)))))
  944. (if (listp sexp)
  945. (cond
  946. ((eq (car sexp) 'eudc-set-server)
  947. (delete-region (save-excursion
  948. (backward-sexp)
  949. (point))
  950. (point))
  951. (setq set-server-p t))
  952. ((and (eq (car sexp) 'setq)
  953. (eq (cadr sexp) 'eudc-server-hotlist))
  954. (delete-region (save-excursion
  955. (backward-sexp)
  956. (point))
  957. (point))
  958. (setq set-hotlist-p t))
  959. ((and (eq (car sexp) 'provide)
  960. (equal (cadr sexp) '(quote eudc-options-file)))
  961. (setq provide-p t)))
  962. (if (and provide-p
  963. set-hotlist-p
  964. set-server-p)
  965. (throw 'found t))))))
  966. (if (eq (point-min) (point-max))
  967. (princ ";; This file was automatically generated by eudc.el.\n\n"))
  968. (or provide-p
  969. (princ "(provide 'eudc-options-file)\n"))
  970. (or (bolp)
  971. (princ "\n"))
  972. (delete-blank-lines)
  973. (princ "(eudc-set-server ")
  974. (prin1 eudc-server)
  975. (princ " '")
  976. (prin1 eudc-protocol)
  977. (princ " t)\n")
  978. (princ "(setq eudc-server-hotlist '")
  979. (prin1 eudc-server-hotlist)
  980. (princ ")\n")
  981. (save-buffer))))
  982. (defun eudc-move-to-next-record ()
  983. "Move to next record, in a buffer displaying directory query results."
  984. (interactive)
  985. (if (not (derived-mode-p 'eudc-mode))
  986. (error "Not in a EUDC buffer")
  987. (let ((pt (next-overlay-change (point))))
  988. (if (< pt (point-max))
  989. (goto-char (1+ pt))
  990. (error "No more records after point")))))
  991. (defun eudc-move-to-previous-record ()
  992. "Move to previous record, in a buffer displaying directory query results."
  993. (interactive)
  994. (if (not (derived-mode-p 'eudc-mode))
  995. (error "Not in a EUDC buffer")
  996. (let ((pt (previous-overlay-change (point))))
  997. (if (> pt (point-min))
  998. (goto-char pt)
  999. (error "No more records before point")))))
  1000. ;;}}}
  1001. ;;{{{ Menus and keymaps
  1002. (require 'easymenu)
  1003. (defconst eudc-custom-generated-menu (cdr (custom-menu-create 'eudc)))
  1004. (defconst eudc-tail-menu
  1005. `(["---" nil nil]
  1006. ["Query with Form" eudc-query-form
  1007. :help "Display a form to query the directory server"]
  1008. ["Expand Inline Query" eudc-expand-inline
  1009. :help "Query the directory server, and expand the query string before point"]
  1010. ["Insert Record into BBDB" eudc-insert-record-at-point-into-bbdb
  1011. (and (or (featurep 'bbdb)
  1012. (prog1 (locate-library "bbdb") (message "")))
  1013. (overlays-at (point))
  1014. (overlay-get (car (overlays-at (point))) 'eudc-record))
  1015. :help "Insert record at point into the BBDB database"]
  1016. ["Insert All Records into BBDB" eudc-batch-export-records-to-bbdb
  1017. (and (derived-mode-p 'eudc-mode)
  1018. (or (featurep 'bbdb)
  1019. (prog1 (locate-library "bbdb") (message ""))))
  1020. :help "Insert all the records returned by a directory query into BBDB"]
  1021. ["---" nil nil]
  1022. ["Get Email" eudc-get-email
  1023. :help "Get the email field of NAME from the directory server"]
  1024. ["Get Phone" eudc-get-phone
  1025. :help "Get the phone field of name from the directory server"]
  1026. ["List Valid Attribute Names" eudc-get-attribute-list
  1027. :help "Return a list of valid attributes for the current server"]
  1028. ["---" nil nil]
  1029. ,(cons "Customize" eudc-custom-generated-menu)))
  1030. (defconst eudc-server-menu
  1031. '(["---" nil nil]
  1032. ["Bookmark Current Server" eudc-bookmark-current-server
  1033. :help "Add current server to the EUDC `servers' hotlist"]
  1034. ["Edit Server List" eudc-edit-hotlist
  1035. :help "Edit the hotlist of directory servers in a specialized buffer"]
  1036. ["New Server" eudc-set-server
  1037. :help "Set the directory server to SERVER using PROTOCOL"]))
  1038. (defun eudc-menu ()
  1039. (let (command)
  1040. (append '("Directory Servers")
  1041. (list
  1042. (append
  1043. '("Server")
  1044. (mapcar
  1045. (function
  1046. (lambda (servspec)
  1047. (let* ((server (car servspec))
  1048. (protocol (cdr servspec))
  1049. (proto-name (symbol-name protocol)))
  1050. (setq command (intern (concat "eudc-set-server-"
  1051. server
  1052. "-"
  1053. proto-name)))
  1054. (if (not (fboundp command))
  1055. (fset command
  1056. `(lambda ()
  1057. (interactive)
  1058. (eudc-set-server ,server (quote ,protocol))
  1059. (message "Selected directory server is now %s (%s)"
  1060. ,server
  1061. ,proto-name))))
  1062. (vector (format "%s (%s)" server proto-name)
  1063. command
  1064. :style 'radio
  1065. :selected `(equal eudc-server ,server)))))
  1066. eudc-server-hotlist)
  1067. eudc-server-menu))
  1068. eudc-tail-menu)))
  1069. (defun eudc-install-menu ()
  1070. (cond
  1071. ((and (featurep 'xemacs) (featurep 'menubar))
  1072. (add-submenu '("Tools") (eudc-menu)))
  1073. ((not (featurep 'xemacs))
  1074. (cond
  1075. ((fboundp 'easy-menu-create-menu)
  1076. (define-key
  1077. global-map
  1078. [menu-bar tools directory-search]
  1079. (cons "Directory Servers"
  1080. (easy-menu-create-menu "Directory Servers" (cdr (eudc-menu))))))
  1081. ((fboundp 'easy-menu-add-item)
  1082. (let ((menu (eudc-menu)))
  1083. (easy-menu-add-item nil '("tools") (easy-menu-create-menu (car menu)
  1084. (cdr menu)))))
  1085. ((fboundp 'easy-menu-create-keymaps)
  1086. (easy-menu-define eudc-menu-map eudc-mode-map "Directory Client Menu" (eudc-menu))
  1087. (define-key
  1088. global-map
  1089. [menu-bar tools eudc]
  1090. (cons "Directory Servers"
  1091. (easy-menu-create-keymaps "Directory Servers"
  1092. (cdr (eudc-menu))))))
  1093. (t
  1094. (error "Unknown version of easymenu"))))
  1095. ))
  1096. ;;; Load time initializations :
  1097. ;;; Load the options file
  1098. (if (and (not noninteractive)
  1099. (and (locate-library eudc-options-file)
  1100. (progn (message "") t)) ; Remove mode line message
  1101. (not (featurep 'eudc-options-file)))
  1102. (load eudc-options-file))
  1103. ;;; Install the full menu
  1104. (unless (featurep 'infodock)
  1105. (eudc-install-menu))
  1106. ;;; The following installs a short menu for EUDC at XEmacs startup.
  1107. ;;;###autoload
  1108. (defun eudc-load-eudc ()
  1109. "Load the Emacs Unified Directory Client.
  1110. This does nothing except loading eudc by autoload side-effect."
  1111. (interactive)
  1112. nil)
  1113. ;;;###autoload
  1114. (cond
  1115. ((not (featurep 'xemacs))
  1116. (defvar eudc-tools-menu
  1117. (let ((map (make-sparse-keymap "Directory Servers")))
  1118. (define-key map [phone]
  1119. `(menu-item ,(purecopy "Get Phone") eudc-get-phone
  1120. :help ,(purecopy "Get the phone field of name from the directory server")))
  1121. (define-key map [email]
  1122. `(menu-item ,(purecopy "Get Email") eudc-get-email
  1123. :help ,(purecopy "Get the email field of NAME from the directory server")))
  1124. (define-key map [separator-eudc-email] menu-bar-separator)
  1125. (define-key map [expand-inline]
  1126. `(menu-item ,(purecopy "Expand Inline Query") eudc-expand-inline
  1127. :help ,(purecopy "Query the directory server, and expand the query string before point")))
  1128. (define-key map [query]
  1129. `(menu-item ,(purecopy "Query with Form") eudc-query-form
  1130. :help ,(purecopy "Display a form to query the directory server")))
  1131. (define-key map [separator-eudc-query] menu-bar-separator)
  1132. (define-key map [new]
  1133. `(menu-item ,(purecopy "New Server") eudc-set-server
  1134. :help ,(purecopy "Set the directory server to SERVER using PROTOCOL")))
  1135. (define-key map [load]
  1136. `(menu-item ,(purecopy "Load Hotlist of Servers") eudc-load-eudc
  1137. :help ,(purecopy "Load the Emacs Unified Directory Client")))
  1138. map))
  1139. (fset 'eudc-tools-menu (symbol-value 'eudc-tools-menu)))
  1140. (t
  1141. (let ((menu '("Directory Servers"
  1142. ["Load Hotlist of Servers" eudc-load-eudc t]
  1143. ["New Server" eudc-set-server t]
  1144. ["---" nil nil]
  1145. ["Query with Form" eudc-query-form t]
  1146. ["Expand Inline Query" eudc-expand-inline t]
  1147. ["---" nil nil]
  1148. ["Get Email" eudc-get-email t]
  1149. ["Get Phone" eudc-get-phone t])))
  1150. (if (not (featurep 'eudc-autoloads))
  1151. (if (featurep 'xemacs)
  1152. (if (and (featurep 'menubar)
  1153. (not (featurep 'infodock)))
  1154. (add-submenu '("Tools") menu))
  1155. (require 'easymenu)
  1156. (cond
  1157. ((fboundp 'easy-menu-add-item)
  1158. (easy-menu-add-item nil '("tools")
  1159. (easy-menu-create-menu (car menu)
  1160. (cdr menu))))
  1161. ((fboundp 'easy-menu-create-keymaps)
  1162. (define-key
  1163. global-map
  1164. [menu-bar tools eudc]
  1165. (cons "Directory Servers"
  1166. (easy-menu-create-keymaps "Directory Servers"
  1167. (cdr menu)))))))))))
  1168. ;;}}}
  1169. (provide 'eudc)
  1170. ;;; eudc.el ends here