Support keyword filtering (records having keywords a and b but not c and d) (#256)

* Support keyword filtering (records having keywords a and b but not c and d)

* Remove debug statements.

* Left a pass statement by mistake.

* Update cli help to show word list '[...]' after search options.

* Add space after method doc.

* Change cli option '-W --without' to '-x --exclude'
This commit is contained in:
SaltyCatFish 2018-03-25 13:40:06 -04:00 committed by Arun Prakash Jana
parent 474c403521
commit e12d2f95f0
2 changed files with 57 additions and 3 deletions

39
buku.py
View File

@ -1273,6 +1273,25 @@ class BukuDb:
stag_results = self.search_by_tag(''.join(stag)) stag_results = self.search_by_tag(''.join(stag))
return list(set(keyword_results) & set(stag_results)) return list(set(keyword_results) & set(stag_results))
def exclude_results_from_search(self, search_results, without, deep):
"""Excludes records that match keyword search using without parameters
Parameters
----------
search_results : list
List of search results
without : list of str
Keywords to search.
deep : bool, optional
True to search for matching substrings.
Returns
-------
list or None
List of search results, or None if no matches.
"""
return list(set(search_results) - set(self.searchdb(without, False, deep)))
def compactdb(self, index, delay_commit=False): def compactdb(self, index, delay_commit=False):
"""When an entry at index is deleted, move the """When an entry at index is deleted, move the
last entry in DB to index, if index is lesser. last entry in DB to index, if index is lesser.
@ -4089,14 +4108,16 @@ POSITIONAL ARGUMENTS:
search_grp = argparser.add_argument_group( search_grp = argparser.add_argument_group(
title='SEARCH OPTIONS', title='SEARCH OPTIONS',
description=''' -s, --sany find records with ANY matching keyword description=''' -s, --sany [...] find records with ANY matching keyword
this is the default search option this is the default search option
-S, --sall find records matching ALL the keywords -S, --sall [...] find records matching ALL the keywords
special keywords - special keywords -
"blank": entries with empty title/tag "blank": entries with empty title/tag
"immutable": entries with locked title "immutable": entries with locked title
-x, --exclude [...] combine with keyword search to exclude
records
--deep match substrings ('pen' matches 'opens') --deep match substrings ('pen' matches 'opens')
-r, --sreg run a regex search -r, --sreg [...] run a regex search
-t, --stag [tag [,|+] ...] [- tag, ...] -t, --stag [tag [,|+] ...] [- tag, ...]
search bookmarks by tags search bookmarks by tags
use ',' to find entries matching ANY tag use ',' to find entries matching ANY tag
@ -4109,6 +4130,7 @@ POSITIONAL ARGUMENTS:
addarg('-r', '--sreg', nargs='*', help=HIDE) addarg('-r', '--sreg', nargs='*', help=HIDE)
addarg('--deep', action='store_true', help=HIDE) addarg('--deep', action='store_true', help=HIDE)
addarg('-t', '--stag', nargs='*', help=HIDE) addarg('-t', '--stag', nargs='*', help=HIDE)
addarg('-x', '--exclude', nargs='*', help=HIDE)
# ------------------------ # ------------------------
# ENCRYPTION OPTIONS GROUP # ENCRYPTION OPTIONS GROUP
@ -4352,6 +4374,7 @@ POSITIONAL ARGUMENTS:
search_opted = True search_opted = True
update_search_results = False update_search_results = False
tags_search = True if (args.stag is not None and len(args.stag)) else False tags_search = True if (args.stag is not None and len(args.stag)) else False
exclude_results = True if (args.exclude is not None and len(args.exclude)) else False
if args.sany is not None: if args.sany is not None:
if len(args.sany): if len(args.sany):
@ -4361,6 +4384,8 @@ POSITIONAL ARGUMENTS:
else: else:
# Search URLs, titles, tags for any keyword # Search URLs, titles, tags for any keyword
search_results = bdb.searchdb(args.sany, False, args.deep) search_results = bdb.searchdb(args.sany, False, args.deep)
if exclude_results:
search_results = bdb.exclude_results_from_search(search_results, args.exclude, args.deep)
else: else:
logerr('no keyword') logerr('no keyword')
elif args.sall is not None: elif args.sall is not None:
@ -4371,8 +4396,16 @@ POSITIONAL ARGUMENTS:
else: else:
# Search URLs, titles, tags with all keywords # Search URLs, titles, tags with all keywords
search_results = bdb.searchdb(args.sall, True, args.deep) search_results = bdb.searchdb(args.sall, True, args.deep)
if exclude_results:
search_results = bdb.exclude_results_from_search(search_results, args.exclude, args.deep)
else: else:
logerr('no keyword') logerr('no keyword')
elif args.exclude is not None:
if len(args.exclude) and len(search_results):
exclude_results = bdb.search_keywords_and_filter_by_tags(args.exclude, True, args.deep, False, None)
search_results = list(set(search_results) - set(exclude_results))
elif args.sreg is not None: elif args.sreg is not None:
if len(args.sreg): if len(args.sreg):
# Apply tag filtering, if opted # Apply tag filtering, if opted

View File

@ -1330,6 +1330,27 @@ def test_search_keywords_and_filter_by_tags(keyword_results, stag_results, exp_r
assert exp_res == res assert exp_res == res
@pytest.mark.parametrize(
'search_results, exclude_results, exp_res',
[
([], [], []),
(['item1', 'item2'], ['item2'], ['item1']),
(['item2'], ['item1'], ['item2']),
(['item1', 'item2'], ['item1', 'item2'], []),
]
)
def test_exclude_results_from_search(search_results, exclude_results, exp_res):
"""test method."""
# init
import buku
bdb = buku.BukuDb()
bdb.searchdb = mock.Mock(return_value=exclude_results)
# test
res = bdb.exclude_results_from_search(
search_results, [], True)
assert exp_res == res
# Helper functions for testcases # Helper functions for testcases