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.
This commit is contained in:
parent
6446221d20
commit
d8c85ae51b
|
@ -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."
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
"Return the id's of books whose title is TITLE."
|
||||
(flatten-list (sqlite-select (calibre--db)
|
||||
"SELECT id FROM books WHERE title = ?"
|
||||
`[,title])))
|
||||
(defmacro calibre-db--query (query arg fuzzy)
|
||||
"Run QUERY on the Calibre database.
|
||||
|
||||
(defun calibre-db--get-author-books (author)
|
||||
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."
|
||||
"SELECT id FROM books WHERE title %s")
|
||||
|
||||
(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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
7
etc/NEWS
7
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.
|
||||
|
|
Loading…
Reference in New Issue