[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[elpa] externals/ebdb b661aac 124/350: First generalized version of snar
From: |
Eric Abrahamsen |
Subject: |
[elpa] externals/ebdb b661aac 124/350: First generalized version of snarfing |
Date: |
Mon, 14 Aug 2017 11:46:20 -0400 (EDT) |
branch: externals/ebdb
commit b661aacf72668220997fac0b5911c59fbda5f763
Author: Eric Abrahamsen <address@hidden>
Commit: Eric Abrahamsen <address@hidden>
First generalized version of snarfing
Doesn't actually work yet, though
---
ebdb-snarf.el | 309 +++++++++++++++++++++++++++++++++-------------------------
1 file changed, 176 insertions(+), 133 deletions(-)
diff --git a/ebdb-snarf.el b/ebdb-snarf.el
index aaa0eb9..1a5ac80 100644
--- a/ebdb-snarf.el
+++ b/ebdb-snarf.el
@@ -1,6 +1,6 @@
;;; ebdb-snarf.el --- Creating or displaying records based on free-form pieces
of text -*- lexical-binding: t; -*-
-;; Copyright (C) 2016 Eric Abrahamsen
+;; Copyright (C) 2016 Free Software Foundation, Inc.
;; Author: Eric Abrahamsen <address@hidden>
;; Keywords: mail
@@ -52,6 +52,34 @@
(require 'ebdb)
+(defgroup ebdb-snarf nil
+ "Options for EBDB snarfing."
+ :group 'ebdb)
+
+(defcustom ebdb-snarf-routines
+ '((ebdb-field-mail "\\([^[:space:]:<address@hidden:[:space:]>]+\\)"))
+
+ "An alist of EBDB field classes and related regexps.
+
+Each alist element is an EBDB field class symbol, followed by a
+list of regular expressions that can be used to produce instances
+of that class when passed to `ebdb-parse'."
+
+ :group 'ebdb-snarf
+ :type 'list)
+
+(defcustom ebdb-snarf-name-re
+ (list "\\(?:[[:upper:]][[:lower:]]+[,[:space:]]*\\)\\{1,\\}")
+
+ "A list of regular expressions matching names.
+
+This is a separate option from `ebdb-snarf-routines' because
+snarfing doesn't search for names separately, only in conjunction
+with other field types."
+
+ :group 'ebdb-snarf
+ :type 'list)
+
;;;###autoload
(defun ebdb-snarf (&optional string start end)
"Snarf text and attempt to display/update/create a record from it.
@@ -86,10 +114,8 @@ information, and do its best to either associate that
information
with existing records, or to organize it into likely groups. If
RECORDS is given, it should be a list of records that we think
have something to do with the text in the buffer."
- (let ((name-re "\\(?:From: \\|To: \\|Cc:
\\)?\\(\\(?:[[:upper:]][[:lower:]]+[,[:space:]]*\\)\\{1,\\}\\)")
- (mail-re "\\([^[:space:]:<address@hidden:[:space:]>]+\\)")
- bundles
- (case-fold-search nil))
+ (let ((case-fold-search nil)
+ bundles)
;; The structure we'll return is a list of lists. Each element is
;; a list of objects (records and fields) that we believe are
@@ -98,12 +124,12 @@ have something to do with the text in the buffer."
(when records
(setq bundles (mapcar (lambda (r)
- (list :record r))
+ (list r))
records)))
;; We don't explicitly search for names, because how would you
;; know? Instead, we look for things that appear to be names,
- ;; that come right before some other field information. Ie:
+ ;; that come right before some other field information. Eg:
;; John Bob <address@hidden>
@@ -113,77 +139,81 @@ have something to do with the text in the buffer."
;; 1111 Upsidedown Drive
;; Nowhere, Massachusetts, 55555
- ;; Currently the definition of "name" is a series of capitalized
- ;; words, which is dumb. Also, the code only scans for mail
- ;; addresses at the moment.
-
- (goto-char (point-min))
-
- (while (re-search-forward mail-re nil t)
- (let* ((mail (match-string-no-properties 1))
- (sticker (line-end-position))
- (name (save-excursion
- (goto-char (progn (line-beginning-position)))
- (when (re-search-forward name-re sticker t)
- (string-trim (match-string-no-properties 1)))))
- ;; Make a regular expression that stands a chance of
- ;; matching an existing record.
- (mail-user-re
- (regexp-opt
- (append (split-string
- (downcase (car (split-string mail "@")))
- "[-_.]" t)
- (when name
- (split-string
- (downcase name)
- "[, ]" t)))))
- group)
- ;; See if any of this information fits what we've got in
- ;; RECORDS.
- (unless (catch 'match
- (dolist (elt bundles)
- (let ((r (plist-get elt :record))
- (n (plist-get elt :names))
- (m (plist-get elt :mail))
- (case-fold-search t))
- (when (cond
- ((and r
- (ebdb-search (list r)
- `((name ,mail-user-re)
- (mail ,mail-user-re)))))
- ((and m
- (ebdb-field-search m mail-user-re)))
- ((and n
- (seq-find
- (lambda (na)
- (ebdb-field-search na mail-user-re))
- n)))
- (t nil))
- (unless (and m ; mail is already in here.
- (string-match-p (ebdb-string m) mail))
- (plist-put
- elt
- :mail (ebdb-parse ebdb-default-mail-class mail)))
- (when (and name
- (or (null n)
- (null (seq-find
- (lambda (na)
- (string-match-p
- (ebdb-string na)
- name))
- n))))
- (plist-put
- elt
- :names (append (list (ebdb-parse ebdb-field-name
name))
- n)))
- (throw 'match t)))))
- (setq group
- (plist-put group :mail (ebdb-parse ebdb-default-mail-class
mail)))
- (when name
- (setq group (plist-put
- group
- :names (list (ebdb-parse ebdb-field-name name)))))
- (push group bundles))))
+ ;; For each individual regular expression, we scan the whole
+ ;; buffer and create single field-class instances from any
+ ;; matches, and possibly an accompanying name-class instance, and
+ ;; decide what to do about both of them.
+ (dolist (class ebdb-snarf-routines)
+ (dolist (re (cdr class))
+ (goto-char (point-min))
+ (while (re-search-forward re nil t)
+ (condition-case nil
+ (let* ((found (ebdb-parse
+ (car class)
+ (match-string-no-properties 1)))
+ (sticker (line-end-position))
+ (name (save-excursion
+ (goto-char (progn (line-beginning-position)))
+ (when (re-search-forward
+ (concat
+ ;; When snarfing messages, we're
+ ;; likely to see email headers in the
+ ;; message body.
+ "\\(?:From: \\|To: \\|Cc: \\)?"
+ (regexp-opt ebdb-snarf-name-re))
+ sticker t)
+ ;; If something goes wrong with the
+ ;; name, don't worry about it.
+ (ignore-errors
+ (ebdb-parse
+ ebdb-field-name
+ (string-trim (match-string-no-properties
1)))))))
+ ;; Make a regular expression that stands a chance
+ ;; of matching an existing record or record
+ ;; fields.
+ (generic-re
+ (regexp-opt
+ (append (split-string
+ (downcase (ebdb-string found))
+ "address@hidden" t)
+ (when name
+ (split-string
+ (downcase (ebdb-string name))
+ "[, ]" t)))))
+ group)
+ ;; See if any of this information fits what we've got in
+ ;; RECORDS.
+ (unless (catch 'match
+ (dolist (b bundles)
+ (dolist (elt b)
+ (when (cond
+ ((and (object-of-class-p elt ebdb-record)
+ (ebdb-search (list elt)
+ `((name ,generic-re)
+ (mail ,generic-re)))))
+ ((and (object-of-class-p elt ebdb-field)
+ (ebdb-field-search elt generic-re)))
+ (t nil))
+ (unless (assoc-string
+ (ebdb-string found)
+ (mapcar #'ebdb-string b))
+ (push found b))
+ (when (and name
+ (null (assoc-string
+ (ebdb-string name)
+ (mapcar #'ebdb-string b))))
+ (push name b))
+ (throw 'match t)))))
+ ;; If it doesn't, make a new grouping.
+ (push found group)
+ (when name
+ (push name group))
+ (push group bundles)))
+ ;; If a regular expression matches but the result is
+ ;; unparseable, that means the regexp is bad and should be
+ ;; changed. Later, report these errors if `ebdb-debug' is
+ ;; true.
+ (ebdb-unparseable nil)))))
bundles))
(defun ebdb-snarf-process (input)
@@ -192,72 +222,85 @@ have something to do with the text in the buffer."
Either find the matching records, update existing (but
incomplete) records, or create new records. Then display them."
- (let (record names name-alist name mail phone address slot records)
- (dolist (bundle input)
- (setq record (plist-get bundle :record)
- names (plist-get bundle :names)
- mail (plist-get bundle :mail)
- phone (plist-get bundle :phone)
- address (plist-get bundle :address))
+ (dolist (bundle input)
+ (let (record names name-alist mails fields records)
+ ;; Record instances, name-class instances, and mail-class
+ ;; instances need to be treated specially.
+ (dolist (elt bundle)
+ (cond ((object-of-class-p elt ebdb-record)
+ (setq record elt))
+ ((object-of-class-p elt ebdb-field-name)
+ (push elt names))
+ ((object-of-class-p elt ebdb-field-mail)
+ (push elt mails))
+ ((object-of-class-p elt ebdb-field)
+ (push elt fields))
+ (t nil)))
;; Either we were given a record...
(unless record
;; ...we can find one in the database...
(unless (setq record (car-safe
- (ebdb-message-search
- (and names (regexp-opt (mapcar #'ebdb-string
names)))
- (and mail (ebdb-string mail)))))
+ (ebdb-search
+ (ebdb-records)
+ (append
+ (when names
+ `(name ,(regexp-opt
+ (mapcar #'ebdb-string names))))
+ (when mails
+ `(mail ,(regexp-opt
+ (mapcar #'ebdb-string mails))))))))
;; ...or we create a new one.
(when (yes-or-no-p
(format "Create new record%s? "
- (cond (names
- (format " for %s"
- (mapconcat #'ebdb-string
- names "/")))
- (mail
- (format " for %s" (ebdb-string mail)))
- (t ""))))
+ (if (or names mails)
+ (format " for %s"
+ (mapconcat #'ebdb-string
+ (append names mails)
+ "/"))
+ "")))
(setq record
(make-instance
ebdb-default-record-class
- :name (progn
- (setq name
- (cond ((= 1 (length names))
- (car names))
- ((setq name-alist
- (mapcar (lambda (n)
- (cons (ebdb-string n)
- n))
- names))
- (cdr
- (assoc-string
- (completing-read
- "Use name: "
- name-alist)
- name-alist)))
- (t nil)))
- (ebdb-read ebdb-default-name-class nil name))))
+ :name (ebdb-read
+ ebdb-default-name-class nil
+ (progn
+ ;; I hate completing read.
+ (cond ((= 1 (length names))
+ (car names))
+ ((setq name-alist
+ (mapcar (lambda (n)
+ (cons (ebdb-string n)
+ n))
+ names))
+ (cdr
+ (assoc-string
+ (completing-read
+ "Use name: "
+ name-alist)
+ name-alist)))
+ (t nil))))))
(ebdb-db-add-record (car ebdb-db-list) record)
- (ebdb-init-record record)
- (when mail
- (ebdb-record-insert-field record 'mail mail)))))
- (dolist (elt (delq nil (list phone address)))
- (setq slot (car (ebdb-record-field-slot-query
- (eieio-object-class record)
- `(nil . ,(eieio-object-class elt)))))
- (when (and slot
- (null (or (equal elt (slot-value record slot))
- (member elt (slot-value record slot))))
- (yes-or-no-p (format "Add %s to %s? "
- (ebdb-string elt)
- (ebdb-string record))))
- (ebdb-record-insert-field
- record
- slot
- elt)))
- (push record records))
- (when records
- (ebdb-display-records records nil nil t (ebdb-popup-window)
- (format "*%s-Snarf*" ebdb-buffer-name)))))
+ (ebdb-init-record record)))))
+ (dolist (elt (append mails fields))
+ ;; What if user said no to creating a new record above?
+ (setq slot (car (ebdb-record-field-slot-query
+ (eieio-object-class record)
+ `(nil . ,(eieio-object-class elt)))))
+ (when (and slot
+ (null (or (equal elt (slot-value record slot))
+ (member elt (slot-value record slot))))
+ (yes-or-no-p (format "Add %s to %s? "
+ (ebdb-string elt)
+ (ebdb-string record))))
+ (ebdb-record-insert-field
+ record
+ slot
+ elt)
+ (ebdb-init-field elt record)))
+ (push record records))
+ (when records
+ (ebdb-display-records records nil nil t (ebdb-popup-window)
+ (format "*%s-Snarf*" ebdb-buffer-name))))
(provide 'ebdb-snarf)
;;; ebdb-snarf.el ends here
- [elpa] externals/ebdb dd13813 171/350: Bah, fixups to a19ff0a, (continued)
- [elpa] externals/ebdb dd13813 171/350: Bah, fixups to a19ff0a, Eric Abrahamsen, 2017/08/14
- [elpa] externals/ebdb f1448f4 174/350: Remove this empty file, Eric Abrahamsen, 2017/08/14
- [elpa] externals/ebdb 6479c87 173/350: Remove unused code, Eric Abrahamsen, 2017/08/14
- [elpa] externals/ebdb db930f6 163/350: Compiler-inspired fixes, and removal of old-code references, Eric Abrahamsen, 2017/08/14
- [elpa] externals/ebdb 281c61e 154/350: Don't ((lambda ()), Eric Abrahamsen, 2017/08/14
- [elpa] externals/ebdb 4cce4c8 096/350: Simplify role field adoption process, Eric Abrahamsen, 2017/08/14
- [elpa] externals/ebdb 0910ddd 105/350: Change default of ebdb-default-user-field, Eric Abrahamsen, 2017/08/14
- [elpa] externals/ebdb b495e29 083/350: Omnibus changes to display and redisplay, Eric Abrahamsen, 2017/08/14
- [elpa] externals/ebdb 29bf304 101/350: Rework MUA window popups, Eric Abrahamsen, 2017/08/14
- [elpa] externals/ebdb 81e23c3 134/350: Simplify record mail citing, Eric Abrahamsen, 2017/08/14
- [elpa] externals/ebdb b661aac 124/350: First generalized version of snarfing,
Eric Abrahamsen <=
- [elpa] externals/ebdb aae57ff 139/350: Ensure that extra name field instances go in 'aka slot, Eric Abrahamsen, 2017/08/14
- [elpa] externals/ebdb 057c4c0 144/350: Have ebdb-snarf accept optional records argument, Eric Abrahamsen, 2017/08/14
- [elpa] externals/ebdb 8d81a19 132/350: Add EBDB record citation, Eric Abrahamsen, 2017/08/14
- [elpa] externals/ebdb 7662133 140/350: Simplify ebdb-dwim-mail, Eric Abrahamsen, 2017/08/14
- [elpa] externals/ebdb 0cfe1ec 164/350: Provide keybinding for ebdb-format-all-records, Eric Abrahamsen, 2017/08/14
- [elpa] externals/ebdb bf51b58 161/350: Fix ebdb-delete-redundant-mails, Eric Abrahamsen, 2017/08/14
- [elpa] externals/ebdb 279eb56 169/350: Tweaks and additions to manual, Eric Abrahamsen, 2017/08/14
- [elpa] externals/ebdb eea0abf 165/350: VCard export is good enough, Eric Abrahamsen, 2017/08/14
- [elpa] externals/ebdb 64b5e43 170/350: Fix autoloads in ebdb.el, Eric Abrahamsen, 2017/08/14
- [elpa] externals/ebdb a968dd3 143/350: First actually-working version of snarfing, Eric Abrahamsen, 2017/08/14