Add dash search script sqlite3 example and Python docset data
Signed-off-by: Christopher Arndt <chris@chrisarndt.de>
This commit is contained in:
		
							parent
							
								
									011f10f9ae
								
							
						
					
					
						commit
						ef67582aa4
					
				
							
								
								
									
										
											BIN
										
									
								
								beispiele/Python_3.docset.zip
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								beispiele/Python_3.docset.zip
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										113
									
								
								beispiele/dash-search.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										113
									
								
								beispiele/dash-search.py
									
									
									
									
									
										Executable file
									
								
							@ -0,0 +1,113 @@
 | 
				
			|||||||
 | 
					#!/usr/bin/env python
 | 
				
			||||||
 | 
					"""Search Dash/Zeal-compatible docset(s) for given search term."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import argparse
 | 
				
			||||||
 | 
					import os
 | 
				
			||||||
 | 
					import pathlib
 | 
				
			||||||
 | 
					import plistlib
 | 
				
			||||||
 | 
					import sqlite3
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					IDX_PATH = pathlib.PurePath("Contents", "Resources", "docSet.dsidx")
 | 
				
			||||||
 | 
					DOC_PATH = pathlib.PurePath("Contents", "Resources", "Documents")
 | 
				
			||||||
 | 
					EXACT_SEARCH_SQL = """\
 | 
				
			||||||
 | 
					SELECT name, path
 | 
				
			||||||
 | 
					    FROM searchIndex
 | 
				
			||||||
 | 
					    WHERE name = ?
 | 
				
			||||||
 | 
					    COLLATE UNICODE_NOCASE
 | 
				
			||||||
 | 
					    LIMIT {limit:d};
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					LIKE_SEARCH_SQL = """\
 | 
				
			||||||
 | 
					SELECT name, path
 | 
				
			||||||
 | 
					    FROM searchIndex
 | 
				
			||||||
 | 
					    WHERE name LIKE ? ESCAPE '\\'
 | 
				
			||||||
 | 
					    COLLATE UNICODE_NOCASE
 | 
				
			||||||
 | 
					    LIMIT {limit:d};
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def get_docsets_dir():
 | 
				
			||||||
 | 
					    docsets_dir = os.getenv("DASH_DOCSETS_PATH")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if not docsets_dir:
 | 
				
			||||||
 | 
					        data_home = pathlib.Path(os.getenv("XDG_DATA_HOME", pathlib.Path.home() / '.local' / 'share'))
 | 
				
			||||||
 | 
					        docsets_dir = data_home / "Zeal" / "Zeal" / "docsets"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return docsets_dir
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def get_docset_indices(name=None):
 | 
				
			||||||
 | 
					    result = []
 | 
				
			||||||
 | 
					    for p in get_docsets_dir().iterdir():
 | 
				
			||||||
 | 
					        if p.is_dir() and p.suffix == ".docset":
 | 
				
			||||||
 | 
					            if name:
 | 
				
			||||||
 | 
					                info_path = p / "Contents" / "Info.plist"
 | 
				
			||||||
 | 
					                with open(info_path, "rb") as fp:
 | 
				
			||||||
 | 
					                    info = plistlib.load(fp)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if not info.get("CFBundleIdentifier") == name.lower():
 | 
				
			||||||
 | 
					                    continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            result.append((p / IDX_PATH, p / DOC_PATH))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return sorted(result)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def get_docset_index(name):
 | 
				
			||||||
 | 
					    return get_docsets_dir() / (name + ".docset") / IDX_PATH
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Custom collation, maybe it is more efficient to store strings
 | 
				
			||||||
 | 
					def unicode_nocase_collation(a: str, b: str):
 | 
				
			||||||
 | 
					    if a.casefold() == b.casefold():
 | 
				
			||||||
 | 
					        return 0
 | 
				
			||||||
 | 
					    if a.casefold() < b.casefold():
 | 
				
			||||||
 | 
					        return -1
 | 
				
			||||||
 | 
					    return 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def main(args=None):
 | 
				
			||||||
 | 
					    ap = argparse.ArgumentParser(usage=__doc__.splitlines()[0])
 | 
				
			||||||
 | 
					    ap.add_argument("-l", "--limit", type=int, default=10, metavar="INT",
 | 
				
			||||||
 | 
					        help="Set maximum number of search results (default: %(default)i)")
 | 
				
			||||||
 | 
					    ap.add_argument("searchphrase", help="Phrase to search for. You can prefix the docset to search in separated by a colon, e.g. 'js:alert'")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    args = ap.parse_args(args)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        prefix, search = (x.strip() for x in args.searchphrase.split(":", 1))
 | 
				
			||||||
 | 
					    except (TypeError, ValueError):
 | 
				
			||||||
 | 
					        search = args.searchphrase.strip()
 | 
				
			||||||
 | 
					        prefix = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    indices = get_docset_indices(prefix)
 | 
				
			||||||
 | 
					    search = search.replace("\\", r"\\\\")
 | 
				
			||||||
 | 
					    search = search.replace("%", "\\%")
 | 
				
			||||||
 | 
					    search = search.replace("_", "\\_")
 | 
				
			||||||
 | 
					    search = "%" + search + "%"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for index, docroot in indices:
 | 
				
			||||||
 | 
					        with sqlite3.connect(index) as cnx:
 | 
				
			||||||
 | 
					            cnx.create_collation("UNICODE_NOCASE", unicode_nocase_collation)
 | 
				
			||||||
 | 
					            cur = cnx.cursor()
 | 
				
			||||||
 | 
					            cur.execute(EXACT_SEARCH_SQL.format(limit=args.limit), (search,))
 | 
				
			||||||
 | 
					            results = {name: path for name,path in cur.fetchall()}
 | 
				
			||||||
 | 
					            num_results = len(results)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if num_results < args.limit:
 | 
				
			||||||
 | 
					                cur.execute(LIKE_SEARCH_SQL.format(limit=args.limit), (search,))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                for i, (name, path) in enumerate(cur.fetchall()):
 | 
				
			||||||
 | 
					                    if name not in results:
 | 
				
			||||||
 | 
					                        results[name] = path
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    if num_results + i + 1 >= args.limit:
 | 
				
			||||||
 | 
					                        break
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for i, name in enumerate(results):
 | 
				
			||||||
 | 
					            file = path.split("#", 1)[0] if '#' in path else path
 | 
				
			||||||
 | 
					            print(f"{i+1} - {name}: {file}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if __name__ == '__main__':
 | 
				
			||||||
 | 
					    import sys
 | 
				
			||||||
 | 
					    sys.exit(main() or 0)
 | 
				
			||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user