Continuous search at prompt. New key q to quit.
This commit is contained in:
parent
8441cebd50
commit
0e936c13a4
@ -49,7 +49,8 @@ Though a terminal utility, it's possible to add bookmarks to `buku` without touc
|
||||
|
||||
## Features
|
||||
|
||||
- Add, open, tag, comment on, search, update, remove, shorten URLs
|
||||
- Add, open, tag, comment on, update, remove, shorten URLs
|
||||
- Multiple search options, continuous search at prompt
|
||||
- Portable, merge-able database, to sync between systems
|
||||
- Import/export bookmarks in markdown or HTML (FF, Chrome compatible)
|
||||
- Fetch page title from web, refresh all titles in a go
|
||||
@ -169,7 +170,7 @@ Shell completion scripts for Bash, Fish and Zsh can be found in respective subdi
|
||||
"immutable": entries with locked title
|
||||
--deep match substrings ('pen' matches 'opened')
|
||||
--sreg expr run a regex search
|
||||
--stag [...] search bookmarks by tag
|
||||
--stag [...] search bookmarks by a tag
|
||||
list tags alphabetically, if no arguments
|
||||
|
||||
encryption options:
|
||||
@ -206,7 +207,7 @@ Shell completion scripts for Bash, Fish and Zsh can be found in respective subdi
|
||||
|
||||
prompt keys:
|
||||
1-N browse search result indices and/or ranges
|
||||
double Enter exit buku
|
||||
q, double Enter exit buku
|
||||
|
||||
symbols:
|
||||
> title
|
||||
@ -239,7 +240,7 @@ Shell completion scripts for Bash, Fish and Zsh can be found in respective subdi
|
||||
- --sall : match all the keywords in URL, title or tags.
|
||||
- --deep : match **substrings** (`match` matches `rematched`) in URL, title and tags.
|
||||
- --sreg : match a regular expression (ignores --deep).
|
||||
- --stag : search bookmarks by tag, or show all tags alphabetically.
|
||||
- --stag : search bookmarks by a tag, or show all tags alphabetically (if no arguments).
|
||||
- Search results are indexed serially. This index is different from actual database index of a bookmark record which is shown in bold within `[]` after the URL.
|
||||
- **Encryption** is optional and manual. AES256 algorithm is used. To use encryption, the database file should be unlocked (-k) before using buku and locked (-l) afterwards. Between these 2 operations, the database file lies unencrypted on the disk, and NOT in memory. Also, note that the database file is *unencrypted on creation*.
|
||||
|
||||
|
9
buku.1
9
buku.1
@ -9,7 +9,8 @@ is a command-line tool to save, tag and search bookmarks.
|
||||
.PP
|
||||
.B Features
|
||||
.PP
|
||||
* Add, open, tag, comment on, search, update, remove, shorten URLs
|
||||
* Add, open, tag, comment on, update, remove, shorten URLs
|
||||
* Multiple search options, continuous search at prompt
|
||||
* Portable merge-able database, to sync between systems
|
||||
* Import/export bookmarks in markdown or HTML (FF, Chrome compatible)
|
||||
* Fetch page title from web, refresh all titles in a go
|
||||
@ -52,7 +53,7 @@ Bookmarks with immutable titles are listed with bold '(L)' after the URL.
|
||||
- --sall : match all the keywords in URL, title or tags.
|
||||
- --deep : match \fBsubstrings\fR (`match` matches `rematched`) in URL, title and tags.
|
||||
- --sreg : match a regular expression (ignores --deep).
|
||||
- --stag : search bookmarks by tag, or show all tags alphabetically.
|
||||
- --stag : search bookmarks by a tag, or show all tags alphabetically (if no arguments).
|
||||
- Search results are indexed serially. This index is different from actual database index of a bookmark record which is shown in bold within '[]' after the URL.
|
||||
.PP
|
||||
\fIEncryption\fR is optional and manual. AES256 algorithm is used. To use encryption, the database file should be unlocked (-k) before using buku and locked (-l) afterwards. Between these 2 operations, the database file lies unencrypted on the disk, and NOT in memory. Also, note that the database file is \fBunencrypted on creation\fR.
|
||||
@ -110,7 +111,7 @@ Scan for a regular expression match.
|
||||
Search modifier to match substrings. Works with --sany, --sall.
|
||||
.TP
|
||||
.BI \--stag " [...]"
|
||||
Search bookmarks by tag. List all tags alphabetically, if no arguments.
|
||||
Search bookmarks by a tag. List all tags alphabetically, if no arguments.
|
||||
.SH ENCRYPTION OPTIONS
|
||||
.TP
|
||||
.BI \-l " " \--lock " [N]"
|
||||
@ -195,7 +196,7 @@ Open the
|
||||
.I Nth
|
||||
search result in browser. Multiple bookmarks are opened if ranges or space-separated result indices are specified.
|
||||
.TP
|
||||
.BI "double Enter"
|
||||
.BI "q, double Enter"
|
||||
Exit buku.
|
||||
.SH ENVIRONMENT
|
||||
.TP
|
||||
|
114
buku.py
114
buku.py
@ -353,6 +353,7 @@ class BukuDb:
|
||||
self.field_filter = field_filter
|
||||
self.immutable = immutable
|
||||
self.chatty = chatty
|
||||
self.deep_search = False # Is deep search opted
|
||||
|
||||
@staticmethod
|
||||
def get_default_dbdir():
|
||||
@ -812,35 +813,37 @@ class BukuDb:
|
||||
|
||||
arguments = []
|
||||
query = 'SELECT id, url, metadata, tags, desc FROM bookmarks WHERE'
|
||||
# Non-deep query string
|
||||
q1 = '(tags REGEXP ? OR URL REGEXP ? OR metadata REGEXP ? OR desc \
|
||||
REGEXP ?)'
|
||||
# Deep query string
|
||||
q2 = "(tags LIKE ('%' || ? || '%') OR URL LIKE ('%' || ? || '%') OR \
|
||||
q1 = "(tags LIKE ('%' || ? || '%') OR URL LIKE ('%' || ? || '%') OR \
|
||||
metadata LIKE ('%' || ? || '%') OR desc LIKE ('%' || ? || '%'))"
|
||||
# Non-deep query string
|
||||
q2 = '(tags REGEXP ? OR URL REGEXP ? OR metadata REGEXP ? OR desc \
|
||||
REGEXP ?)'
|
||||
|
||||
if regex:
|
||||
for token in keywords:
|
||||
query = '%s %s OR' % (query, q1)
|
||||
query = '%s %s OR' % (query, q2)
|
||||
|
||||
arguments += (token, token, token, token)
|
||||
query = query[:-3]
|
||||
elif all_keywords:
|
||||
for token in keywords:
|
||||
if not deep:
|
||||
token = '\\b' + token + '\\b'
|
||||
if deep:
|
||||
query = '%s %s AND' % (query, q1)
|
||||
self.deep_search = True
|
||||
else:
|
||||
token = '\\b' + token + '\\b'
|
||||
query = '%s %s AND' % (query, q2)
|
||||
|
||||
arguments += (token, token, token, token)
|
||||
query = query[:-4]
|
||||
elif not all_keywords:
|
||||
for token in keywords:
|
||||
if not deep:
|
||||
token = '\\b' + token + '\\b'
|
||||
if deep:
|
||||
query = '%s %s OR' % (query, q1)
|
||||
self.deep_search = True
|
||||
else:
|
||||
token = '\\b' + token + '\\b'
|
||||
query = '%s %s OR' % (query, q2)
|
||||
|
||||
arguments += (token, token, token, token)
|
||||
@ -863,10 +866,11 @@ class BukuDb:
|
||||
def search_by_tag(self, tag):
|
||||
'''Search and list bookmarks with a tag
|
||||
|
||||
:param tag: tag to search
|
||||
:param tag: a tag to search as string
|
||||
:return: search results, or None, if no matches
|
||||
'''
|
||||
|
||||
tag = '%s%s%s' % (DELIM, tag.strip(DELIM), DELIM)
|
||||
query = "SELECT id, url, metadata, tags, desc FROM bookmarks \
|
||||
WHERE tags LIKE '%' || ? || '%' ORDER BY id ASC"
|
||||
logger.debug('query: "%s", args: %s', query, tag)
|
||||
@ -1603,21 +1607,31 @@ def parse_tags(keywords=None):
|
||||
return '%s%s%s' % (DELIM, DELIM.join(sorted_tags), DELIM)
|
||||
|
||||
|
||||
def prompt(results, noninteractive=False):
|
||||
def prompt(obj, results, noninteractive=False):
|
||||
'''Show each matching result from a search and prompt
|
||||
|
||||
:param obj: a valid instance of BukuDb class
|
||||
:param results: result set from a DB query
|
||||
:param noninteractive: do not seek user input
|
||||
'''
|
||||
|
||||
count = 0
|
||||
for row in results:
|
||||
count += 1
|
||||
print_record(row, count)
|
||||
|
||||
if noninteractive:
|
||||
new_results = True
|
||||
if not type(obj) is BukuDb:
|
||||
logger.error('Not a BukuDb instance')
|
||||
return
|
||||
|
||||
while True:
|
||||
if results and new_results:
|
||||
count = 0
|
||||
print()
|
||||
|
||||
for row in results:
|
||||
count += 1
|
||||
print_record(row, count)
|
||||
|
||||
if noninteractive:
|
||||
return
|
||||
|
||||
try:
|
||||
nav = input('Results, ranges (x-y,(a)ll) to open: ')
|
||||
if not nav:
|
||||
@ -1628,7 +1642,60 @@ def prompt(results, noninteractive=False):
|
||||
except EOFError:
|
||||
return
|
||||
|
||||
# open all results and re-prompt if 'a' is pressed
|
||||
# search ANY match with new keywords
|
||||
if nav.startswith('s ') and len(nav) > 2:
|
||||
results = obj.searchdb(nav[2:].split(), False, obj.deep_search)
|
||||
new_results = True
|
||||
continue
|
||||
|
||||
# search ALL match with new keywords
|
||||
if nav.startswith('S ') and len(nav) > 2:
|
||||
results = obj.searchdb(nav[2:].split(), True, obj.deep_search)
|
||||
new_results = True
|
||||
continue
|
||||
|
||||
# regular expressions search with new keywords
|
||||
if nav.startswith('r ') and len(nav) > 2:
|
||||
results = obj.searchdb(nav[2:].split(), True, regex=True)
|
||||
new_results = True
|
||||
continue
|
||||
|
||||
# tag search with new keywords
|
||||
if nav.startswith('t ') and len(nav) > 2:
|
||||
results = obj.search_by_tag(nav[2:])
|
||||
new_results = True
|
||||
continue
|
||||
|
||||
# list tags with 't'
|
||||
if nav == 't':
|
||||
obj.list_tags()
|
||||
results = None
|
||||
new_results = False
|
||||
continue
|
||||
|
||||
# quit with 'q'
|
||||
if nav == 'q':
|
||||
return
|
||||
|
||||
# toggle deep search with 'd'
|
||||
if nav == 'd':
|
||||
obj.deep_search = not obj.deep_search
|
||||
if obj.deep_search:
|
||||
print('deep search on')
|
||||
else:
|
||||
print('deep search off')
|
||||
|
||||
new_results = False
|
||||
continue
|
||||
|
||||
new_results = False
|
||||
|
||||
# Nothing to browse if there are no results
|
||||
if not results:
|
||||
print('Not in a search context')
|
||||
continue
|
||||
|
||||
# open all results and re-prompt with 'a'
|
||||
if nav == 'a':
|
||||
for index in range(0, count):
|
||||
try:
|
||||
@ -1665,6 +1732,7 @@ def prompt(results, noninteractive=False):
|
||||
logger.error('%s(), ln %d: %s',
|
||||
func, linenumber, e)
|
||||
else:
|
||||
print('Invalid input')
|
||||
break
|
||||
|
||||
|
||||
@ -1888,7 +1956,7 @@ class ExtendedArgumentParser(argparse.ArgumentParser):
|
||||
file.write('''
|
||||
prompt keys:
|
||||
1-N browse search result indices and/or ranges
|
||||
double Enter exit buku
|
||||
q, double Enter exit buku
|
||||
|
||||
symbols:
|
||||
> title
|
||||
@ -2009,7 +2077,7 @@ def main():
|
||||
"immutable": entries with locked title
|
||||
--deep match substrings ('pen' matches 'opened')
|
||||
--sreg expr run a regex search
|
||||
--stag [...] search bookmarks by tag
|
||||
--stag [...] search bookmarks by a tag
|
||||
list tags alphabetically, if no arguments''')
|
||||
addarg = search_grp.add_argument
|
||||
addarg('-s', '--sany', nargs='+', help=HIDE)
|
||||
@ -2215,8 +2283,7 @@ def main():
|
||||
elif tagsearch:
|
||||
search_opted = True
|
||||
if len(args.stag) > 0:
|
||||
tag = '%s%s%s' % (DELIM, ' '.join(args.stag).strip(DELIM), DELIM)
|
||||
search_results = bdb.search_by_tag(tag)
|
||||
search_results = bdb.search_by_tag(' '.join(args.stag))
|
||||
else:
|
||||
bdb.list_tags()
|
||||
|
||||
@ -2227,8 +2294,9 @@ def main():
|
||||
oneshot = True
|
||||
|
||||
if not args.json:
|
||||
prompt(search_results, oneshot)
|
||||
prompt(bdb, search_results, oneshot)
|
||||
else:
|
||||
# Printing in Json format is non-interactive
|
||||
print(format_json(search_results, field_filter=args.format))
|
||||
|
||||
# Delete search results if opted
|
||||
|
Loading…
Reference in New Issue
Block a user