From d6e030417762c2e7f6952272e350c2500443ce9f Mon Sep 17 00:00:00 2001 From: cjnaz Date: Sat, 6 Jun 2020 19:23:27 -0700 Subject: [PATCH] v1.1 initial --- xbsjsonedit | 143 +++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 109 insertions(+), 34 deletions(-) diff --git a/xbsjsonedit b/xbsjsonedit index a17cdc4..dcb083e 100755 --- a/xbsjsonedit +++ b/xbsjsonedit @@ -1,12 +1,28 @@ #!/usr/bin/env python3 -"""xBrowserSync json backup editor""" +"""xBrowserSync json backup editor -__version__ = "v1.0 200330" +Usages: + Command line modes: + xbsjsonedit news prints bookmarks tagged with 'news' + xbsjsonedit news --bookmarks prints bookmarks tagged with 'news' and any bookmarks or URLs containing 'news' + xbsjsonedit --print prints all bookmarks with their hierarchy + xbsjsonedit --tags 5 prints only tag names that are used at least 5 times + xbsjsonedit --tags 5 --names prints tag names and their bookmarks that are used at least 5 times + xbsjsonedit --tags 0 --names prints tag names and their bookmark that are used exactly 1 time + + Interactive mode: + xbsjsonedit Enters interactive mode + + The path to the xbs_backup*.json file defaults to the newest in the download directory. --jsonbackup overrides the default. +""" + +__version__ = "v1.1 200604" #========================================================== # -# Chris Nelson 2018 - 2019 +# Chris Nelson 2018 - 2020 # +# 200604 v1.1 Added cli search features, defaulted .json file to be the newest in download directory. # 200330 v1.0 Merged pull request #3 from mblais (add unbuffered getch input; add 'all' option), Reworked --tags and --names switches. # 191206 v0.3 Added tags dump # 191205 v0.2 Updated to xbrowsersync v1.5 and Python 3.x ONLY @@ -18,11 +34,12 @@ __version__ = "v1.0 200330" #========================================================== import argparse -import os.path +import os import json import codecs import sys import io +import glob # Configs / Constants @@ -30,6 +47,16 @@ INDENT = 2 OFILESUFFIX = "_OUT" NONE_SEARCH = "__NONE__" +if sys.platform == "win32": + JSON_PATH_DEFAULT = "Z:\\Downloads\\" +if "linux" in sys.platform: # on Py2, on Py3 + JSON_PATH_DEFAULT = "/mnt/share/Downloads/" + +py_version = float(sys.version_info.major) + float(sys.version_info.minor)/10 +if py_version < 3.6: + print ("Not supported on Python versions before 3.6.") + sys.exit() + # Global items url_dict = {} folder_dict = {} @@ -45,14 +72,16 @@ def main(): global folder_dict global tags_dict - with io.open(args.Infile, encoding='utf8', errors="replace") as json_data: + with io.open(infile, encoding='utf8', errors="replace") as json_data: json_dict = json.load(json_data) term = NONE_SEARCH + # CLI mode if args.print: digin(parent=json_dict["xbrowsersync"]["data"]["bookmarks"], parent_id="", path="/", search_term="", operation="printtree") - exit() + sys.exit() + if args.tags > -1: digin(parent=json_dict["xbrowsersync"]["data"]["bookmarks"], parent_id="", path="/", search_term="", operation="GatherTags") for tag in sorted(tags_dict.keys()): @@ -69,8 +98,32 @@ def main(): for bookmark in tags_dict[tag]: print (" {:<4} - {}".format(bookmark["id"], bookmark["title"])) print() - exit() + sys.exit() + if args.SearchTerm is not None: + _search_term = args.SearchTerm.lower() + print (f"***** Bookmarks tagged with <{_search_term}>:") + digin(parent=json_dict["xbrowsersync"]["data"]["bookmarks"], parent_id="", path="/", search_term="", operation="GatherTags") + for tag in sorted(tags_dict.keys()): + if _search_term in tag.lower(): + print (f" [{tag}] ({len(tags_dict[tag])}):") + for bookmark in tags_dict[tag]: + print (f" {bookmark['title']}") + print() + + if args.bookmarks: + match_cnt = 0 + print (f"\n***** Bookmarks containing or tagged with <{_search_term}>:") + digin(parent=json_dict["xbrowsersync"]["data"]["bookmarks"], parent_id="", path="/", search_term=_search_term, operation="search2") + + sys.exit() + + if args.bookmarks: # and not args.SearchTerm + print ("A SearchTerm is required with use of the --bookmark switch.") + sys.exit() + + + # Interactive mode while (1): match_cnt = 0 change_cnt = 0 @@ -103,7 +156,7 @@ Options: q: Quit/exit (Do a Write first!) """.format(term)) - select = prompt("Enter option: ", valid="stTgGdDfFxXyYwq") + select = prompt("Enter option: ", valid="pstTgGdDfFxXyYwq") if select == 'p': digin(parent=json_dict["xbrowsersync"]["data"]["bookmarks"], parent_id="", path="/", search_term="", operation="printtree") @@ -166,17 +219,19 @@ Options: print ("\nMatches: {} Deletes: {}\n".format(match_cnt, change_cnt)) elif select == 'w': - ans = prompt_long("Output file name (default <{}>: ".format(args.Infile + OFILESUFFIX)) + # ans = prompt_long("Output file name (default <{}>: ".format(args.Infile + OFILESUFFIX)) + ans = prompt_long("Output file name (default <{}>: ".format(infile + OFILESUFFIX)) if ans == "": - ans = args.Infile + OFILESUFFIX + # ans = args.Infile + OFILESUFFIX + ans = infile + OFILESUFFIX with io.open(ans, "w", encoding='utf8') as ofile: json.dump(json_dict, ofile, ensure_ascii=False, indent=2) elif select == 'q': if prompt("Quit now? (Remember to write any changes first!) ", valid="yn") == 'y': - exit() + sys.exit() else: print ("Shouldn't have gotten here!") - exit() + sys.exit() def digin(parent, parent_id, path, search_term, operation, commit=False, indent=""): @@ -198,7 +253,6 @@ def digin(parent, parent_id, path, search_term, operation, commit=False, indent= print ("{:<4} > {}{}".format(item["id"], indent, item["title"])) if operation == "SearchFolders": - # local_changes = False if search_term in item["title"].lower(): print ("{:<4} > {}{}".format(item["id"], indent, item["title"])) match_cnt += 1 @@ -249,7 +303,7 @@ def digin(parent, parent_id, path, search_term, operation, commit=False, indent= if len(item["children"]) == 0: print ("\n{:4} - {} >>> {}".format( item["id"], - path, #[3:], + path, item["title"])) match_cnt += 1 if commit: @@ -295,7 +349,7 @@ def digin(parent, parent_id, path, search_term, operation, commit=False, indent= if operation == "DupURLs": log_urls (item["url"], path, item["id"], parent, parent_id, item_index) - if operation == "search": + if operation == "search" or operation == "search2": match = False if search_term in item["url"].lower() or search_term in item["title"].lower(): match = True @@ -307,16 +361,19 @@ def digin(parent, parent_id, path, search_term, operation, commit=False, indent= if match: match_cnt += 1 - print ("\n{:4} - {} >>> {}\n url: <{}>".format( - item["id"], - path[3:], - item["title"], - item["url"])) - if "tags" in item: - if item["tags"] == None: - print (" tags = None !!!") - else: - print (" tags: <{}>".format(item["tags"])) + if operation == "search": + print ("\n{:4} - {} >>> {}\n url: <{}>".format( + item["id"], + path[3:], + item["title"], + item["url"])) + if "tags" in item: + if item["tags"] == None: + print (" tags = None !!!") + else: + print (" tags: <{}>".format(item["tags"])) + if operation == "search2": + print (f" {item['title']}") if commit: if do_all: @@ -349,7 +406,7 @@ path_dict = {} def collect_items (path, parent, index): """Collect items for later deletion by delete_items.""" global path_dict - # print (path) # "/ > [xbs] Toolbar > Chris' > Blogs166" + # print (path) # "/ > [xbs] Toolbar > Chris' > Blogs166" # print (parent) # List of dictionaries of bookmarks [{}, {}, {}] # print (index) # Index within the list - 2 if path not in path_dict: @@ -565,22 +622,40 @@ getch = _Getch() if __name__ == '__main__': parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawTextHelpFormatter) - parser.add_argument('Infile', - help="json backup file.") + parser.add_argument('SearchTerm', nargs='?', # optional positional argument + help="Print bookmarks tagged with this text (CLI mode).") + parser.add_argument('--bookmarks', '-b', action='store_true', + help="Print bookmarks containing or tagged with the SearchTerm (CLI mode).") parser.add_argument('--print', '-p', action='store_true', - help="Print bookmark hierarchy (redirect to less or a file).") + help="Print complete bookmark hierarchy (redirect to less or a file) (CLI mode).") parser.add_argument('--tags', '-t', type=int, default=-1, - help="Print tags list filtered by tags with a minimum number of uses. n=0 prints only single use tags.") + help="Print tags list filtered by tags with a minimum number of uses. n=0 prints only single use tags. (CLI mode)") parser.add_argument('--names', '-n', action='store_true', - help="Used with --tags to enable printing the titles/names for each tag.") + help="Used with --tags to enable printing the titles/names for each tag. (CLI mode)") + parser.add_argument('--jsonbackup', '-j', type=str, + help=f"Path to json backup file. Default is newest version at <{JSON_PATH_DEFAULT}xbs_backup*.json>.") parser.add_argument('-V', '--version', help="Return version number and exit.", action='version', version='%(prog)s ' + __version__) args = parser.parse_args() - if not os.path.exists(args.Infile): - print ("Can't find the input file <{}>".format(args.Infile)) - exit() + infile = args.jsonbackup + if infile is None: + infile = "__none__" + list_of_files = glob.glob(f'{JSON_PATH_DEFAULT}xbs_backup*.json') + # if is_Linux: + # list_of_files = glob.glob(f'{JSON_PATH_DEFAULT_LINUX}xbs_backup*.json') + # if is_Windows: + # list_of_files = glob.glob(f'{JSON_PATH_DEFAULT_WINDOWS}xbs_backup*.json') + # for file in list_of_files: + # print (file) + infile = max(list_of_files, key=os.path.getctime) + # print ("latest: ", infile) + # exit() + + if not os.path.exists(infile): + print ("Can't find the input file <{}>".format(infile)) + sys.exit() main()