emacs-elpa-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[elpa] externals/calibre d8c85ae51b 8/9: Enable fuzzy searching


From: ELPA Syncer
Subject: [elpa] externals/calibre d8c85ae51b 8/9: Enable fuzzy searching
Date: Tue, 16 Jan 2024 12:57:41 -0500 (EST)

branch: externals/calibre
commit d8c85ae51bafde748c512f980dd54077d0ef7511
Author: Kjartan Oli Agustsson <kjartanoli@disroot.org>
Commit: Kjartan Oli Agustsson <kjartanoli@disroot.org>

    Enable fuzzy searching
    
    * calibre-cli.el: (calibre-cli--search-operation): Add a new argument
      docstring, specifying the docstring of the generated function.
      Add a new argument fuzzy-match to the generated function.
      Condition the search query of the generated function on the value of
      fuzzy-match.
    * calibre-db.el (calibre-db--get-title-books):
      (calibre-db--query): New macro to generate SQL queries with
      potentially fuzzy matching and handling their output.
      (calibre-db--search-function): New macro to generate search functions.
      (calibre-db--get-title-books):
      (calibre-db--get-author-books):
      (calibre-db--get-tag-books):
      (calibre-db--get-publisher-books):
      (calibre-db--get-series-books):
      (calibre-db--get-format-books):
    
      Define using calibre-db--search-function.
    
    * calibre-search.el (calibre-search--fuzzy-search-p): New predicate
      function.
      (calibre-search--make-filter):
      (calibre-search--make-composite-filter-component):
    
      New functions.
    
      (calibre-library--search-function):
      (calibre-search): Add option for fuzzy searching.
      (calibre-search--composition-function): Handle transient arguments
      consistently with calibre-library--search-function.
    * doc/calibre.texi (Virtual Libraries): Document fuzzy filters.
    * etc/NEWS: Mention the new fuzzy search functionality.
---
 calibre-cli.el    | 25 +++++++++++---------
 calibre-core.el   | 19 ++++++++-------
 calibre-db.el     | 70 +++++++++++++++++++++++++++++++++++--------------------
 calibre-search.el | 55 +++++++++++++++++++++++++++++++++----------
 doc/calibre.texi  | 19 ++++++++++++---
 etc/NEWS          |  7 +++++-
 6 files changed, 134 insertions(+), 61 deletions(-)

diff --git a/calibre-cli.el b/calibre-cli.el
index 2be09163fc..8f948b1e29 100644
--- a/calibre-cli.el
+++ b/calibre-cli.el
@@ -1,6 +1,6 @@
 ;;; calibre-cli.el --- Fallback CLI interface when SQLite is not available  
-*- lexical-binding: t; -*-
 
-;; Copyright (C) 2023  Free Software Foundation, Inc.
+;; Copyright (C) 2023,2024  Free Software Foundation, Inc.
 
 ;; This file is part of calibre.el.
 
@@ -97,9 +97,12 @@ AUTHORS should be a comma separated string."
   "Return the File Name of the book whose files are FILES."
   (file-name-base (car files)))
 
-(defmacro calibre-cli--search-operation (field)
-  "Create a function to search for books matching FIELD."
-  `(defun ,(intern (format "calibre-cli--get-%s-books" field)) (,field)
+(defmacro calibre-cli--search-operation (field docstring)
+  "Create a function to search for books matching FIELD.
+
+DOCSTRING is the docstring of the created function."
+  `(defun ,(intern (format "calibre-cli--get-%s-books" field)) (,field 
&optional fuzzy-match)
+     ,docstring
      (with-temp-buffer
        (call-process calibre-calibredb-executable
                      nil
@@ -108,18 +111,18 @@ AUTHORS should be a comma separated string."
                      "search"
                      "--with-library"
                      (calibre--library)
-                     (format ,(format "%s:=%%s" field) ,field))
+                     (format ,(format "%s%%s%%s" field ) (if fuzzy-match ":" 
":=") ,field))
        (mapcar #'cl-parse-integer
                (string-split
                 (buffer-substring-no-properties (point-min) (point-max))
                 ",")))))
 
-(calibre-cli--search-operation title)
-(calibre-cli--search-operation series)
-(calibre-cli--search-operation publisher)
-(calibre-cli--search-operation format)
-(calibre-cli--search-operation tag)
-(calibre-cli--search-operation author)
+(calibre-cli--search-operation title "Return the id's of books whose title is 
TITLE.")
+(calibre-cli--search-operation series "Return the id's of books that are part 
of SERIES.")
+(calibre-cli--search-operation publisher "Return the id's of books published 
by PUBLISHER.")
+(calibre-cli--search-operation format "Return the id's of books available in 
FORMAT.")
+(calibre-cli--search-operation tag "Return the id's of books tagged with TAG.")
+(calibre-cli--search-operation author "Return the id's of books written by 
AUTHOR.")
 
 (defun calibre-cli--get-titles ()
   "Return a list of the titles in the active library."
diff --git a/calibre-core.el b/calibre-core.el
index 4e5ecf2623..bc8721b112 100644
--- a/calibre-core.el
+++ b/calibre-core.el
@@ -1,6 +1,6 @@
 ;;; calibre-core.el --- Abstract interface for the Calibre Library  -*- 
lexical-binding: t; -*-
 
-;; Copyright (C) 2023  Free Software Foundation, Inc.
+;; Copyright (C) 2023,2024  Free Software Foundation, Inc.
 
 ;; This file is part of calibre.el.
 
@@ -219,14 +219,15 @@ BOOK is a `calibre-book'."
                    (if (eq op '+)
                        '()
                      (calibre--books))))
-    (seq-let (_ field value) filter
-      (cl-case field
-        (title (calibre-core--interface get-title-books value))
-        (author (calibre-core--interface get-author-books value))
-        (tag (calibre-core--interface get-tag-books value))
-        (publisher (calibre-core--interface get-publisher-books value))
-        (series (calibre-core--interface get-series-books value))
-        (format (calibre-core--interface get-format-books value))))))
+    (seq-let (_ field value &rest params) filter
+      (let ((fuzzy-match (seq-contains-p params '~)))
+        (cl-case field
+        (title (calibre-core--interface get-title-books value fuzzy-match))
+        (author (calibre-core--interface get-author-books value fuzzy-match))
+        (tag (calibre-core--interface get-tag-books value fuzzy-match))
+        (publisher (calibre-core--interface get-publisher-books value 
fuzzy-match))
+        (series (calibre-core--interface get-series-books value fuzzy-match))
+        (format (calibre-core--interface get-format-books value 
fuzzy-match)))))))
 
 (defun calibre-library--filter (filters books)
   "Return those books in BOOKS that match FILTERS.
diff --git a/calibre-db.el b/calibre-db.el
index 20ffc031f8..5e49d702d4 100644
--- a/calibre-db.el
+++ b/calibre-db.el
@@ -1,6 +1,6 @@
 ;;; calibre-db.el --- Interact with the Calibre database -*- lexical-binding:t 
-*-
 
-;; Copyright (C) 2023  Free Software Foundation, Inc.
+;; Copyright (C) 2023,2024  Free Software Foundation, Inc.
 
 ;; Author: Kjartan Oli Agustsson <kjartanoli@disroot.org>
 ;; Maintainer: Kjartan Oli Agustsson <kjartanoli@disroot.org>
@@ -151,50 +151,70 @@ FROM books
 LEFT JOIN books_series_link sl ON books.id = sl.book
 LEFT JOIN series ON sl.series = series.id;"))))
 
-(defun calibre-db--get-title-books (title)
+(defmacro calibre-db--query (query arg fuzzy)
+  "Run QUERY on the Calibre database.
+
+ARG is a value to passed to a WHERE clause in QUERY.  FUZZY
+determines whether the WHERE clause matches using LIKE or exact
+matching.  If FUZZY is non-nil LIKE will be used.  QUERY must contain exactly 
one %s where the matching against ARG should be substituted.
+
+This means QUERY should probably end:
+WHERE column %s
+with column being the name of some column."
+  `(flatten-list (sqlite-select (calibre--db)
+                                (format ,query (if ,fuzzy "LIKE ?" "= ?"))
+                                (if fuzzy-match
+                                    (vector (format "%%%s%%" ,arg))
+                                  (vector ,arg)))))
+
+(defmacro calibre-db--search-function (field docstring query)
+  "Create a search function for FIELD with DOCSTRING as docstring.
+
+QUERY is the SQL query used to perform the search, suitable as an
+argument to `calibre-db--query'."
+  (declare (indent 1))
+  `(defun ,(intern (format "calibre-db--get-%s-books" field))
+       (,field &optional fuzzy-match)
+     ,docstring
+     (calibre-db--query ,query ,field fuzzy-match)))
+
+(calibre-db--search-function title
   "Return the id's of books whose title is TITLE."
-  (flatten-list (sqlite-select (calibre--db)
-                               "SELECT id FROM books WHERE title = ?"
-                               `[,title])))
+  "SELECT id FROM books WHERE title %s")
 
-(defun calibre-db--get-author-books (author)
+(calibre-db--search-function author
   "Return the id's of books written by AUTHOR."
-  (flatten-list (sqlite-select (calibre--db)
-                               "SELECT book
+  "SELECT book
 FROM books_authors_link al
 LEFT JOIN authors a ON al.author = a.id
-WHERE a.name = ?" `[,author])))
+WHERE a.name %s")
 
-(defun calibre-db--get-tag-books (tag)
+(calibre-db--search-function tag
   "Return the id's of books tagged with TAG."
-  (flatten-list (sqlite-select (calibre--db)
-                               "SELECT book
+  "SELECT book
 FROM books_tags_link tl
 LEFT JOIN tags t ON tl.tag = t.id
-WHERE t.name = ?" `[,tag])))
+WHERE t.name %s")
 
-(defun calibre-db--get-publisher-books (publisher)
+(calibre-db--search-function publisher
   "Return the id's of books published by PUBLISHER."
-  (flatten-list (sqlite-select (calibre--db)
-                               "SELECT book
+  "SELECT book
 FROM books_publishers_link pl
 LEFT JOIN publishers p ON pl.publisher = p.id
-WHERE p.name = ?" `[,publisher])))
+WHERE p.name %s")
 
-(defun calibre-db--get-series-books (series)
+(calibre-db--search-function series
   "Return the id's of books that are part of SERIES."
-  (flatten-list (sqlite-select (calibre--db)
-                               "SELECT book
+  "SELECT book
 FROM books_series_link sl
 LEFT JOIN series s ON sl.series = s.id
-WHERE s.name = ?" `[,series])))
+WHERE s.name %s")
 
-(defun calibre-db--get-format-books (format)
+(calibre-db--search-function format
   "Return the id's of books available in FORMAT."
-  (flatten-list (sqlite-select (calibre--db)
-                               "SELECT book
+  "SELECT book
 FROM data
-WHERE format = ?" `[,format])))
+WHERE format %s")
 
 (provide 'calibre-db)
 ;;; calibre-db.el ends here
diff --git a/calibre-search.el b/calibre-search.el
index 4e22fb84aa..5f32fe9468 100644
--- a/calibre-search.el
+++ b/calibre-search.el
@@ -1,5 +1,5 @@
 ;;; calibre-search.el --- Filter books based on search criteria.  -*- 
lexical-binding: t; -*-
-;; Copyright (C) 2023  Free Software Foundation, Inc.
+;; Copyright (C) 2023,2024  Free Software Foundation, Inc.
 
 ;; This file is part of calibre.el.
 
@@ -59,16 +59,32 @@ ARGS is the argument list of a transient command."
       '-
     '+))
 
+(defun calibre-search--fuzzy-search-p (args)
+  "Return t if the filter operation should use fuzzy matching.
+ARGS is the argument list of a transient command."
+  (seq-contains-p args "--fuzzy"))
+
+(defun calibre-search--make-filter (op field val fuzzy)
+  "Create a search filter.
+
+OP is a symbol, either + or - for inclusive or exclusive.  FIELD
+is a symbol identifying the field to search.  VAL is the value to
+search for.  If FUZZY is non-nil create a fuzzy filter."
+  (if fuzzy
+      `[,op ,field ,val ~]
+    `[,op ,field ,val]))
+
 (defmacro calibre-library--search-function (field)
   "Create a function adding a filter for FIELD."
   `(defun ,(intern (format "calibre-library-search-%s" field)) (val &optional 
args)
      (interactive (list (,(intern (format "calibre-search-chose-%s" field)))
                         (transient-args 'calibre-search)))
-     (setf calibre-library--filters (cons
-                                     (vector (calibre-search--operation args)
-                                             (quote ,(intern field))
-                                             val)
-                                     calibre-library--filters))
+     (push (calibre-search--make-filter
+            (calibre-search--operation args)
+            (quote ,(intern field))
+            val
+            (calibre-search--fuzzy-search-p args))
+           calibre-library--filters)
      (calibre-library--refresh)))
 
 (calibre-library--search-function "title")
@@ -89,7 +105,8 @@ ARGS is the argument list of a transient command."
   "Filter the library view."
   :transient-suffix 'transient--do-call
   ["Arguments"
-   ("-e" "Exclude" "--exclude")]
+   ("e" "Exclude" "--exclude")
+   ("~" "Fuzzy" "--fuzzy")]
   ["Search"
    ("T" "Title" calibre-library-search-title)
    ("a" "Author" calibre-library-search-author)
@@ -121,13 +138,26 @@ ARGS is the argument list of a transient command."
   (interactive)
   (setf calibre-search-composing-filter nil))
 
+(defun calibre-search--make-composite-filter-component (field val fuzzy)
+  "Create a component of a composite search filter.
+
+FIELD is a symbol identifying the field to search.  VAL is the
+value to search for.  If FUZZY is non-nil create a fuzzy filter."
+  (if fuzzy
+      `[,field ,val ~]
+    `[,field ,val]))
+
 (defmacro calibre-search--composition-function (field)
   "Create a function adding a filter for FIELD to a composite filter."
-  `(defun ,(intern (format "calibre-search-compose-%s" field)) ()
+  `(defun ,(intern (format "calibre-search-compose-%s" field)) (val &optional 
args)
      ,(format "Add a filter for %s to the composite filter under 
construction." field)
-     (interactive)
-     (setf calibre-search-composing-filter
-           (cons (vector ',(intern field) (,(intern (format 
"calibre-search-chose-%s" field)))) calibre-search-composing-filter))))
+     (interactive (list (,(intern (format "calibre-search-chose-%s" field)))
+                        (transient-args 'calibre-search-compose)))
+     (push (calibre-search--make-composite-filter-component
+            (quote ,(intern field))
+            val
+            (calibre-search--fuzzy-search-p args))
+           calibre-search-composing-filter)))
 
 (calibre-search--composition-function "title")
 (calibre-search--composition-function "author")
@@ -140,7 +170,8 @@ ARGS is the argument list of a transient command."
   "Create a composite filter."
   :transient-suffix 'transient--do-call
   ["Arguments"
-   ("-e" "Exclude" "--exclude")]
+   ("-e" "Exclude" "--exclude")
+   ("~" "Fuzzy" "--fuzzy")]
   ["Compose"
    ("T" "Title" calibre-search-compose-title)
    ("a" "Author" calibre-search-compose-author)
diff --git a/doc/calibre.texi b/doc/calibre.texi
index a705fef6d1..fb148d0d9e 100644
--- a/doc/calibre.texi
+++ b/doc/calibre.texi
@@ -9,7 +9,7 @@ This manual is for calibre.el (version @value{VERSION},
 @value{UPDATED}), a package for interacting with Calibre libraries from
 Emacs.
 
-Copyright @copyright{} 2023  Free Software Foundation, Inc.
+Copyright @copyright{} 2023,2024  Free Software Foundation, Inc.
 
 @quotation
 Permission is granted to copy, distribute and/or modify this document
@@ -241,11 +241,24 @@ it must be one of:
 @end itemize
 @var{VALUE} is the string to compare @var{FIELD} to.
 
+@cindex fuzzy filter
+For a given book to match against a basic filter the value of
+@var{FIELD} for that book must be exactly equal to @var{VALUE}.
+Sometimes this exact matching is undesirable and you would like to find
+all books where @var{FIELD} contains @var{VALUE} instead.  This is
+exactly what fuzzy filters do.
+
+A fuzzy filter is a vector @code{[@var{OP} @var{FIELD} @var{VALUE} ~]}.
+@var{OP}, @var{FIELD}, and @var{VALUE} have the same meaning as in a
+basic filter.  The @code{~} is what identifies the filter as fuzzy.
+
 @cindex composite filter
 A composite filter, is a vector @code{[@var{OP} @var{FILTERS}]} where
 @var{OP} has the same meaning as for basic filters and @var{FILTERS} is
-a list of vectors @code{[@var{FIELD} @var{VALUE}]}.  In each such vector
-@var{FIELD} and @var{VALUE} have the same meaning as for a basic filter.
+a list of vectors @code{[@var{FIELD} @var{VALUE}]} or @code{[@var{FIELD}
+@var{VALUE} ~]}.  In each such vector @var{FIELD} and @var{VALUE} have
+the same meaning as for a basic filter and a @code{~} at the end marks
+that particular component as fuzzy.
 
 @node Modifying your library
 @chapter Modifying your library
diff --git a/etc/NEWS b/etc/NEWS
index 228914094e..4fd68f6f5d 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -1,6 +1,6 @@
 calibre.el NEWS -*- outline -*-
 
-Copyright (C) 2023 Free Software Foundation, Inc.
+Copyright (C) 2023,2024 Free Software Foundation, Inc.
 See the end of the file for license conditions.
 
 This file is about changes in calibre.el, the Emacs client for
@@ -8,6 +8,11 @@ Calibre.
 
 
 * Changes in calibre.el 1.4.0
+** Enable fuzzy searching
+The search interface now supports 'fuzzy' searching, which matches any
+book whose value for the search field contains the search term as a
+substring.
+
 ** Allow opening books in external programs
 The new calibre-library-open-book-external command allows opening
 books using an external program.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]