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
|
## 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
|
- Portable, merge-able database, to sync between systems
|
||||||
- Import/export bookmarks in markdown or HTML (FF, Chrome compatible)
|
- Import/export bookmarks in markdown or HTML (FF, Chrome compatible)
|
||||||
- Fetch page title from web, refresh all titles in a go
|
- 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
|
"immutable": entries with locked title
|
||||||
--deep match substrings ('pen' matches 'opened')
|
--deep match substrings ('pen' matches 'opened')
|
||||||
--sreg expr run a regex search
|
--sreg expr run a regex search
|
||||||
--stag [...] search bookmarks by tag
|
--stag [...] search bookmarks by a tag
|
||||||
list tags alphabetically, if no arguments
|
list tags alphabetically, if no arguments
|
||||||
|
|
||||||
encryption options:
|
encryption options:
|
||||||
@ -206,7 +207,7 @@ Shell completion scripts for Bash, Fish and Zsh can be found in respective subdi
|
|||||||
|
|
||||||
prompt keys:
|
prompt keys:
|
||||||
1-N browse search result indices and/or ranges
|
1-N browse search result indices and/or ranges
|
||||||
double Enter exit buku
|
q, double Enter exit buku
|
||||||
|
|
||||||
symbols:
|
symbols:
|
||||||
> title
|
> 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.
|
- --sall : match all the keywords in URL, title or tags.
|
||||||
- --deep : match **substrings** (`match` matches `rematched`) in URL, title and tags.
|
- --deep : match **substrings** (`match` matches `rematched`) in URL, title and tags.
|
||||||
- --sreg : match a regular expression (ignores --deep).
|
- --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.
|
- 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*.
|
- **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
|
.PP
|
||||||
.B Features
|
.B Features
|
||||||
.PP
|
.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
|
* Portable merge-able database, to sync between systems
|
||||||
* Import/export bookmarks in markdown or HTML (FF, Chrome compatible)
|
* Import/export bookmarks in markdown or HTML (FF, Chrome compatible)
|
||||||
* Fetch page title from web, refresh all titles in a go
|
* 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.
|
- --sall : match all the keywords in URL, title or tags.
|
||||||
- --deep : match \fBsubstrings\fR (`match` matches `rematched`) in URL, title and tags.
|
- --deep : match \fBsubstrings\fR (`match` matches `rematched`) in URL, title and tags.
|
||||||
- --sreg : match a regular expression (ignores --deep).
|
- --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.
|
- 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
|
.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.
|
\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.
|
Search modifier to match substrings. Works with --sany, --sall.
|
||||||
.TP
|
.TP
|
||||||
.BI \--stag " [...]"
|
.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
|
.SH ENCRYPTION OPTIONS
|
||||||
.TP
|
.TP
|
||||||
.BI \-l " " \--lock " [N]"
|
.BI \-l " " \--lock " [N]"
|
||||||
@ -195,7 +196,7 @@ Open the
|
|||||||
.I Nth
|
.I Nth
|
||||||
search result in browser. Multiple bookmarks are opened if ranges or space-separated result indices are specified.
|
search result in browser. Multiple bookmarks are opened if ranges or space-separated result indices are specified.
|
||||||
.TP
|
.TP
|
||||||
.BI "double Enter"
|
.BI "q, double Enter"
|
||||||
Exit buku.
|
Exit buku.
|
||||||
.SH ENVIRONMENT
|
.SH ENVIRONMENT
|
||||||
.TP
|
.TP
|
||||||
|
104
buku.py
104
buku.py
@ -353,6 +353,7 @@ class BukuDb:
|
|||||||
self.field_filter = field_filter
|
self.field_filter = field_filter
|
||||||
self.immutable = immutable
|
self.immutable = immutable
|
||||||
self.chatty = chatty
|
self.chatty = chatty
|
||||||
|
self.deep_search = False # Is deep search opted
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_default_dbdir():
|
def get_default_dbdir():
|
||||||
@ -812,35 +813,37 @@ class BukuDb:
|
|||||||
|
|
||||||
arguments = []
|
arguments = []
|
||||||
query = 'SELECT id, url, metadata, tags, desc FROM bookmarks WHERE'
|
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
|
# Deep query string
|
||||||
q2 = "(tags LIKE ('%' || ? || '%') OR URL LIKE ('%' || ? || '%') OR \
|
q1 = "(tags LIKE ('%' || ? || '%') OR URL LIKE ('%' || ? || '%') OR \
|
||||||
metadata LIKE ('%' || ? || '%') OR desc LIKE ('%' || ? || '%'))"
|
metadata LIKE ('%' || ? || '%') OR desc LIKE ('%' || ? || '%'))"
|
||||||
|
# Non-deep query string
|
||||||
|
q2 = '(tags REGEXP ? OR URL REGEXP ? OR metadata REGEXP ? OR desc \
|
||||||
|
REGEXP ?)'
|
||||||
|
|
||||||
if regex:
|
if regex:
|
||||||
for token in keywords:
|
for token in keywords:
|
||||||
query = '%s %s OR' % (query, q1)
|
query = '%s %s OR' % (query, q2)
|
||||||
|
|
||||||
arguments += (token, token, token, token)
|
arguments += (token, token, token, token)
|
||||||
query = query[:-3]
|
query = query[:-3]
|
||||||
elif all_keywords:
|
elif all_keywords:
|
||||||
for token in keywords:
|
for token in keywords:
|
||||||
if not deep:
|
if deep:
|
||||||
token = '\\b' + token + '\\b'
|
|
||||||
query = '%s %s AND' % (query, q1)
|
query = '%s %s AND' % (query, q1)
|
||||||
|
self.deep_search = True
|
||||||
else:
|
else:
|
||||||
|
token = '\\b' + token + '\\b'
|
||||||
query = '%s %s AND' % (query, q2)
|
query = '%s %s AND' % (query, q2)
|
||||||
|
|
||||||
arguments += (token, token, token, token)
|
arguments += (token, token, token, token)
|
||||||
query = query[:-4]
|
query = query[:-4]
|
||||||
elif not all_keywords:
|
elif not all_keywords:
|
||||||
for token in keywords:
|
for token in keywords:
|
||||||
if not deep:
|
if deep:
|
||||||
token = '\\b' + token + '\\b'
|
|
||||||
query = '%s %s OR' % (query, q1)
|
query = '%s %s OR' % (query, q1)
|
||||||
|
self.deep_search = True
|
||||||
else:
|
else:
|
||||||
|
token = '\\b' + token + '\\b'
|
||||||
query = '%s %s OR' % (query, q2)
|
query = '%s %s OR' % (query, q2)
|
||||||
|
|
||||||
arguments += (token, token, token, token)
|
arguments += (token, token, token, token)
|
||||||
@ -863,10 +866,11 @@ class BukuDb:
|
|||||||
def search_by_tag(self, tag):
|
def search_by_tag(self, tag):
|
||||||
'''Search and list bookmarks with a 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
|
: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 \
|
query = "SELECT id, url, metadata, tags, desc FROM bookmarks \
|
||||||
WHERE tags LIKE '%' || ? || '%' ORDER BY id ASC"
|
WHERE tags LIKE '%' || ? || '%' ORDER BY id ASC"
|
||||||
logger.debug('query: "%s", args: %s', query, tag)
|
logger.debug('query: "%s", args: %s', query, tag)
|
||||||
@ -1603,13 +1607,24 @@ def parse_tags(keywords=None):
|
|||||||
return '%s%s%s' % (DELIM, DELIM.join(sorted_tags), DELIM)
|
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
|
'''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
|
:param noninteractive: do not seek user input
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
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
|
count = 0
|
||||||
|
print()
|
||||||
|
|
||||||
for row in results:
|
for row in results:
|
||||||
count += 1
|
count += 1
|
||||||
print_record(row, count)
|
print_record(row, count)
|
||||||
@ -1617,7 +1632,6 @@ def prompt(results, noninteractive=False):
|
|||||||
if noninteractive:
|
if noninteractive:
|
||||||
return
|
return
|
||||||
|
|
||||||
while True:
|
|
||||||
try:
|
try:
|
||||||
nav = input('Results, ranges (x-y,(a)ll) to open: ')
|
nav = input('Results, ranges (x-y,(a)ll) to open: ')
|
||||||
if not nav:
|
if not nav:
|
||||||
@ -1628,7 +1642,60 @@ def prompt(results, noninteractive=False):
|
|||||||
except EOFError:
|
except EOFError:
|
||||||
return
|
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':
|
if nav == 'a':
|
||||||
for index in range(0, count):
|
for index in range(0, count):
|
||||||
try:
|
try:
|
||||||
@ -1665,6 +1732,7 @@ def prompt(results, noninteractive=False):
|
|||||||
logger.error('%s(), ln %d: %s',
|
logger.error('%s(), ln %d: %s',
|
||||||
func, linenumber, e)
|
func, linenumber, e)
|
||||||
else:
|
else:
|
||||||
|
print('Invalid input')
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
||||||
@ -1888,7 +1956,7 @@ class ExtendedArgumentParser(argparse.ArgumentParser):
|
|||||||
file.write('''
|
file.write('''
|
||||||
prompt keys:
|
prompt keys:
|
||||||
1-N browse search result indices and/or ranges
|
1-N browse search result indices and/or ranges
|
||||||
double Enter exit buku
|
q, double Enter exit buku
|
||||||
|
|
||||||
symbols:
|
symbols:
|
||||||
> title
|
> title
|
||||||
@ -2009,7 +2077,7 @@ def main():
|
|||||||
"immutable": entries with locked title
|
"immutable": entries with locked title
|
||||||
--deep match substrings ('pen' matches 'opened')
|
--deep match substrings ('pen' matches 'opened')
|
||||||
--sreg expr run a regex search
|
--sreg expr run a regex search
|
||||||
--stag [...] search bookmarks by tag
|
--stag [...] search bookmarks by a tag
|
||||||
list tags alphabetically, if no arguments''')
|
list tags alphabetically, if no arguments''')
|
||||||
addarg = search_grp.add_argument
|
addarg = search_grp.add_argument
|
||||||
addarg('-s', '--sany', nargs='+', help=HIDE)
|
addarg('-s', '--sany', nargs='+', help=HIDE)
|
||||||
@ -2215,8 +2283,7 @@ def main():
|
|||||||
elif tagsearch:
|
elif tagsearch:
|
||||||
search_opted = True
|
search_opted = True
|
||||||
if len(args.stag) > 0:
|
if len(args.stag) > 0:
|
||||||
tag = '%s%s%s' % (DELIM, ' '.join(args.stag).strip(DELIM), DELIM)
|
search_results = bdb.search_by_tag(' '.join(args.stag))
|
||||||
search_results = bdb.search_by_tag(tag)
|
|
||||||
else:
|
else:
|
||||||
bdb.list_tags()
|
bdb.list_tags()
|
||||||
|
|
||||||
@ -2227,8 +2294,9 @@ def main():
|
|||||||
oneshot = True
|
oneshot = True
|
||||||
|
|
||||||
if not args.json:
|
if not args.json:
|
||||||
prompt(search_results, oneshot)
|
prompt(bdb, search_results, oneshot)
|
||||||
else:
|
else:
|
||||||
|
# Printing in Json format is non-interactive
|
||||||
print(format_json(search_results, field_filter=args.format))
|
print(format_json(search_results, field_filter=args.format))
|
||||||
|
|
||||||
# Delete search results if opted
|
# Delete search results if opted
|
||||||
|
Loading…
x
Reference in New Issue
Block a user