branch: externals/greader
commit b76ea154a718ec3652d91564fbb280fb9617dcae
Author: Michelangelo Rodriguez <[email protected]>
Commit: Michelangelo Rodriguez <[email protected]>
feat(dict): Implement non-destructive dictionary merging
This commit introduces a feature to merge multiple dictionary files.
This allows users to combine a primary dictionary with one or more
auxiliary dictionaries (e.g., a personal word list) temporarily for a session
or persistently across sessions, without modifying the main dictionary file.
Key changes:
- A new interactive command `greader-dict-merge-dictionary` allows merging
another dictionary into the current one.
- Merged entries are marked with a `greader-dict-merged` text property and
are not saved to the main dictionary file, ensuring the merge is
non-destructive.
- Merge configurations are stored in
`greader-dict-merge-dictionaries-alist` and can be saved persistently to a
separate `.merges` file, controlled by the `greader-dict-merge-save` custom
variable.
- `greader-dict-read-from-dict-file` and `greader-dict-add` were updated to
handle the merging logic.
---
greader-dict.el | 157 ++++++++++++++++++++++++++++++++++++++++++++++++--------
1 file changed, 136 insertions(+), 21 deletions(-)
diff --git a/greader-dict.el b/greader-dict.el
index 5191ea54d3..098b16df12 100644
--- a/greader-dict.el
+++ b/greader-dict.el
@@ -238,6 +238,113 @@ buffer."
(or greader--current-buffer
greader-dict--current-reading-buffer)))
,@body))
+;; merging dictionaries.
+
+(defun greader-dict--merged-p (key)
+ "Return t if KEY is merged, nil otherwise."
+ (get-text-property 0 'greader-dict-merged key))
+
+(defun greader-dict--merge (key)
+ "Set KEY as merged."
+ (unless (greader-dict--merged-p key)
+ (propertize key 'greader-dict-merged t)))
+
+;; merge customization
+(defvar greader-dict-merge-dictionaries-alist ()
+ "An alist containing merged dictionaries.
+The car of this alist should be the file name of the main dictionary.
+The cdr is a list of the auxiliary files, that is, the dictionaries to
+merge.
+these files should be located in the same directory.
+This is to hobey that you accidentally mix dictionaries for different
+languages.
+The command `greader-dict-merge' is an interface that takes in account
+these restrictions, so feel free to use it.")
+
+(defcustom greader-dict-merge-save 'ask
+ "If t, makes your merges persistent against emacs sessions.
+If it is `ask', then ask if you want to save your merges.
+nil means don't save at all."
+ :type '(choice (const :tag "alwais save" t)
+ (const :tag "ask every time" ask)
+ (const :tag "Do not save at all" nil)))
+
+(defcustom greader-dict-merge-file (file-name-concat
+ greader-dict-directory ".merges")
+ "File in which save the merges."
+ :type '(file))
+
+(defun greader-dict-merge-save (&optional filename)
+ "Save the variable `greader-dict-merge-save' in FILENAME.
+If filename is nil or not specified, use
+`greader-dict-merge-file'."
+ (unless filename
+ (setq filename greader-dict-merge-file))
+ (with-greader-dict-temp-buffer
+ (print greader-dict-merge-dictionaries-alist (current-buffer))
+ (write-region (point-min) (point-max) filename)))
+
+(defun greader-dict-merge-get ()
+ "Return merged dictionaries for this dictionary.
+Return nil if this dictionary does not contain merged dictionaries."
+ (if-let* ((dictionaries (assoc (greader-dict--get-file-name)
greader-dict-merge-dictionaries-alist)))
+ dictionaries
+ nil))
+
+(defun greader-dict-load-merges (&optional filename)
+ "Populate `greader-dict-merge-dictionaries-alist' with contents of
+ FILENAME.
+Return FILENAME contents, or nil if FILENAME is empty or does not
+exist or some other system errors."
+ (unless filename
+ (setq filename greader-dict-merge-file))
+ (with-greader-dict-temp-buffer
+ (if (file-exists-p filename)
+ (progn
+ (insert-file-contents filename)
+ (if-let* ((result (read (current-buffer))))
+ (setq greader-dict-merge-dictionaries-alist result)
+ nil))
+ nil)))
+
+(defun greader-dict-merge--check-candidate (filename)
+ "Predicate for `read-file-name."
+ (if (equal filename (file-name-nondirectory
+ (greader-dict--get-file-name)))
+ t
+ nil))
+
+(declare-function 'greader-dict-merge-dictionary nil)
+(defun greader-dict-merge--dictionaries ()
+ "Merge the current dictionary with the contents of
+`greader-dict-merge-dictionaries-alist."
+ (if-let* ((dictionaries (greader-dict-merge-get)))
+ (dolist (dictionary dictionaries)
+ (greader-dict-merge-dictionary dictionary))
+ nil))
+
+(defun greader-dict-merge-dictionary (&optional filename)
+ "Merge contents of FILENAME in `greader-dictionary'.
+If filename is nil, merge only using
+ `greader-dict-merge-dictionaries-alist'."
+ (interactive
+ (let ((result
+ (read-file-name "Dictionary file to merge: " greader-dict-directory
nil t nil
+ 'greader-dict-merge--check-candidate)))
+ (list result)))
+ (if (null filename)
+ (greader-dict-merge--dictionaries)
+ (greader-dict-merge--dictionaries)
+ (greader-dict-read-from-dict-file filename)))
+
+(defvar-keymap greader-dict-filter-map
+ :doc "key bindings for greader-dict filter feature."
+ "C-r d f a" #'greader-dict-filter-add
+ "C-r d f m" #'greader-dict-filter-modify
+ "C-r r" #'isearch-backward
+ "C-r d f k" #'greader-dict-filter-remove)
+
+
;; filters.
;; filters allow users to define arbitrary regexps to be replaced
;; either with empty strings or by another string.
@@ -378,18 +485,19 @@ as a word definition."
"Amount of idleness to wait before saving dictionary data.
A value of 0 indicates saving immediately."
:type 'number)
-(defun greader-dict-add (word replacement)
- "Add the WORD REPLACEMENT pair to `greader-dictionary'.
+(defun greader-dict-add (word replacement &optional merge)
+ "Add the WORD REPLACEMent pair to `greader-dictionary'.
If you want to add a partial replacement, you should
add `\*'to the end of the WORD string parameter.
-This function only checks that WORD and REPLACEMENT are not the same.
-Other checks for record integrity are responsibility of the caller."
+If MERGE is non-nil, then add the pair as merged."
;; We prevent an infinite loop if disallowing that key and values
;; are the same.
(unless replacement
(setq replacement ""))
(when (string-equal-ignore-case word replacement)
- (user-error "Key and value are the same, aborting"))
+ (user-error "key and value are the same, aborting"))
+ (when merge
+ (setq word (greader-dict--merge word)))
(puthash word replacement greader-dictionary)
(setq greader-dict--saved-flag nil)
(cond
@@ -527,36 +635,42 @@ rebuilding it on every call."
(with-greader-dict-temp-buffer
(maphash
(lambda (k v)
- (insert "\"" k "\"" "=" v "\n"))
+ (unless (greader-dict--merged-p k)
+ (insert "\"" k "\"" "=" v "\n")))
greader-dictionary)
(write-region (point-min) (point-max)
(greader-dict--get-file-name)))
(setq greader-dict--saved-flag t))
-(defun greader-dict-read-from-dict-file (&optional force)
- "Populate `greader-dictionary' with the contents of
+(defun greader-dict-read-from-dict-file
+ (&optional force filename merge)
+ "populate `greader-dictionary' with the contents of
`greader-dict-filename'.
If FORCE is non-nil, reading happens even if there are definitions not
yet saved.
If FORCE is nil \(the default\) then this function generates an
-user-error and aborts the reading process."
+user-error and aborts the reading process.
+If filename is specified, then use that file for reading instead of
+the standard.
+If merge is non-nil, then merge only the definitions."
;; This code is to provide a variable
;; `greader-dictionary' by default usable in the buffer
;; temporary where the replacements defined in
`greader-after-get-sentence-functions' occur.
(when (and (not greader-dict--saved-flag) (not force))
(user-error "Dictionary has been modified and not yet saved"))
- (when (file-exists-p (greader-dict--get-file-name))
- (with-greader-dict-temp-buffer
- (insert-file-contents (greader-dict--get-file-name))
- (when-let* ((lines (string-lines (buffer-string) t)))
- (dolist (line lines)
- (setq line (split-string line "=" ))
- (setf (car line) (car (split-string (car line) "\"" t)))
- (let ((greader-dict-save-after-time -1))
- (greader-dict-add (car line) (car (cdr line)))))
- (greader-dict--filter-init)
- (setq greader-dict--saved-flag t)))
- (add-hook 'buffer-list-update-hook #'greader-dict--update)))
+ (let ((filename (or filename (greader-dict--get-file-name))))
+ (when (file-exists-p filename)
+ (with-greader-dict-temp-buffer
+ (insert-file-contents filename)
+ (when-let* ((lines (string-lines (buffer-string) t)))
+ (dolist (line lines)
+ (setq line (split-string line "=" ))
+ (setf (car line) (car (split-string (car line) "\"" t)))
+ (let ((greader-dict-save-after-time -1))
+ (greader-dict-add (car line) (car (cdr line)) merge)))
+ (greader-dict--filter-init)
+ (setq greader-dict--saved-flag t)))))
+ (add-hook 'buffer-list-update-hook #'greader-dict--update))
;; Command for saving interactively dictionary data.
(defun greader-dict-save ()
@@ -782,6 +896,7 @@ Means it exists a file called \"greader-dict.global\" in
((string-equal "greader-dict.global" greader-dict-filename)
'global)
(t 'global))))
+
(defun greader-dict--type-alternatives ()
"Return the list of currently valid alternatives for dictionary."
(let ((alternatives nil))