Continuous search at prompt. New key q to quit.

This commit is contained in:
Arun Prakash Jana 2016-11-13 23:22:00 +05:30
parent 8441cebd50
commit 0e936c13a4
No known key found for this signature in database
GPG Key ID: A75979F35C080412
3 changed files with 101 additions and 31 deletions

View File

@ -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
View File

@ -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
View File

@ -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