def search_score(self, query): # Also use the executable name, such as "baobab" or "nautilus", but score that lower return max([ get_score(query, self.name), get_score(query, self._executable) * .8, get_score(query, self.description) * .7 ] + [get_score(query, k) * .6 for k in self.keywords])
def append(self, result_item): # get_search_name() returns a string with the app display name, but it may contain a # second line in which case that line is the name of the executable search_fields = result_item.get_search_name() name, exec_name, *_ = '{}\n'.format(search_fields).split('\n') score = max(get_score(self._query, name), get_score(self._query, exec_name) * .8) if score >= self._min_score: result_item.score = -score # use negative to sort by score in desc. order self._items.insert(result_item) while len(self._items) > self._limit: self._items.pop( ) # remove items with the lowest score to maintain limited number of items
def append(self, result_item): score = get_score(self._query, result_item.get_search_name()) if score >= self._min_score: result_item.score = -score # use negative to sort by score in desc. order self._items.insert(result_item) while len(self._items) > self._limit: self._items.pop( ) # remove items with the lowest score to maintain limited number of items
def handle_query(self, query: str) -> List[FileBrowserResult]: try: path = Path(os.path.expandvars(query.strip())).expanduser() results = [] closest_parent = str( next(parent for parent in [path] + list(path.parents) if parent.exists())) remainder = "/".join(path.parts[closest_parent.count('/') + 1:]) if closest_parent == '.': raise RuntimeError(f'Invalid path "{path}"') if not remainder: file_names = self.list_files(str(path), sort_by_atime=True) for name in self.filter_dot_files(file_names)[:self.LIMIT]: file = os.path.join(closest_parent, name) results.append(FileBrowserResult(file)) else: file_names = self.list_files(closest_parent) query = remainder if not query.startswith('.'): file_names = self.filter_dot_files(file_names) sorted_files = sorted(file_names, key=lambda fn: get_score(query, fn), reverse=True) filtered_files = list( filter(lambda fn: get_score(query, fn) > 40, sorted_files))[:self.LIMIT] results = [ FileBrowserResult(os.path.join(closest_parent, name)) for name in filtered_files ] except (RuntimeError, OSError): results = [] return results
def fuzzyfinder(search: str, items: List[str], root_path: str) -> List[str]: """ >>> fuzzyfinder("hallo", ["hi", "hu", "hallo", "false"]) ['hallo', 'false', 'hi', 'hu'] """ scores = [] for i in items: score = get_score(search, get_name_from_path(i, root_path=root_path)) scores.append((score, i)) scores = sorted(scores, key=lambda score: score[0], reverse=True) return list(map(lambda score: score[1], scores))
def score_name_query_match( name_chunks: List[str], query_chunks: List[str], basename_query: str ) -> Tuple[float, float]: """ Score how well given name matches given query using Ulauncher fuzzy search algo """ # Basename match only matters if our search query includes one if len(query_chunks) > 1: basename = ".".join(name_chunks[: len(query_chunks) - 1]) basename_match_score = fuzzy_search.get_score(basename_query, basename) else: basename_match_score = 0 # The last part of the name match is only relevant # if this name has at least that many parts if len(name_chunks) >= len(query_chunks): leafname_match_score = fuzzy_search.get_score( query_chunks[len(query_chunks) - 1], name_chunks[len(query_chunks) - 1] ) else: leafname_match_score = 0 return (basename_match_score, leafname_match_score)
def test_get_score(): assert get_score('fiwebro', 'Firefox Web Browser') > get_score( 'fiwebro', 'Firefox Web SBrowser') assert get_score('j', 'johnny') < get_score('j', 'john') assert get_score('calc', 'LibreOffice Calc') < get_score('calc', 'Calc') assert get_score('calc', 'Contacts') < get_score('calc', 'LibreOffice Calc') assert get_score('pla', 'Pycharm') < get_score('pla', 'Google Play Music') assert get_score('', 'LibreOffice Calc') == 0 assert get_score('0', 'LibreOffice Calc') == 0
def xtest_speed(): t0 = time.time() for _ in range(1000): get_score('fiwebro', 'Firefox Web Browser') print('time for get_score:', (time.time() - t0)) assert 0
def search_fullname(query: str, module_names: Iterable[str]) -> List[str]: """ Rank and sort module names by how well they match a wildcard query. Nesting of the names doesn't matter. Treat first asterisk as a wildcard that can match any part of the name: >>> search_fullname('ht*error', ['http', 'http.cli.error', 'http.cli'])[0] 'http.cli.error' Module names must contain all characters from the first part of the query to be included in the results: >>> search_fullname('ul*action', ['ulauncher.shared.action', 'cozmo.actions']) ['ulauncher.shared.action'] Query can also start with a wildcard, in which case the first part doesn't matter: >>> len(search_fullname('*action', ['http.cli.action', 'boo.action'])) 2 Dots can be used in any part of the query: >>> search_fullname('ht.c*t.', ['http.client.err', 'http.server.request']) ['http.client.err'] Exact matches of the query's tail are scored higher, to allow for quick lookup of modules with known names: >>> search_fullname('*widg', ['ulauncher.api.itemwidget', 'wedge'])[0] 'ulauncher.api.itemwidget' """ # Split the query into 2 parts: head and tail, separated by an asterisk qhead, _, qtail = query.lower().partition("*") # Every character in the "head" part of the query # must be present in the given order, so we turn it into a regex pattern head_regex = ordered_char_list_regex(qhead) if qhead else None result_items = [] for name in module_names: # The search is case insensitive name_lower = name.lower() if head_regex: # If head is present, it regex must match head_match = re.search(head_regex, name_lower) # Head and tail parts of the name don't overlap name_tail_idx = head_match.span()[1] if head_match else 0 name_tail = name_lower[name_tail_idx:] else: # If the wildcard was the first character in the query, # then every name matches it, and we treat the whole name as "tail" name_tail = name_lower if not head_regex or head_match: head_score = fuzzy_search.get_score(qhead, name_lower) if qhead else 0 tail_score = fuzzy_search.get_score(qtail, name_tail) if qtail else 0 result_items.append( FullNameSearchResultItem( module_name=name, head_match_score=head_score, tail_match_score=tail_score, tail_exact_contains=1 if qtail in name_tail else 0, ) ) def sort_key(item): return ( item.tail_exact_contains, item.tail_match_score, item.head_match_score, item.module_name, ) return [res.module_name for res in sorted(result_items, key=sort_key, reverse=True)]
def search_score(self, query): return get_score(query, self.get_name())