deanishe Posted September 9, 2017 Share Posted September 9, 2017 Add fuzzy search to your Script Filters This is a simple script you can add to your Script Filters to replace "Alfred filters results" with a fuzzy search algorithm. https://github.com/deanishe/alfred-fuzzy How it works Instead of calling your script directly, you call it via fuzzy.py, which caches your script's output for the duration of the user session (as long as the user is using your workflow), and filters the titles of the items emitted by your script against the user's query using a fuzzy algorithm. Example usage fuzzy.py only works in Script Filters, and you should run it as a bash/zsh script (i.e. with Language = /bin/bash or Language = /bin/zsh). Instead of running your own script directly, place ./fuzzy.py in front of it. For example, if your Script Filter script looks like this: /usr/bin/python myscript.py You would replace it with: # export user query to `query` environment variable, so `fuzzy.py` can read it export query="$1" # or if you're using "with input as {query}" # export query="{query}" # call your original script via `fuzzy.py` ./fuzzy.py /usr/bin/python myscript.py Note: Don't forget to turn off "Alfred filters results"! Caveats As the script is written in Python and uses a more complex matching algorithm, it can only handle a few thousands items at most before it becomes irritatingly sluggish (Alfred can handle many tens of thousands). If there's interest in the script, I will rewrite it in a compiled language. My Go library uses the same algorithm, and it can comfortably handle 20K+ items. You can grab a demo workflow from GitHub to see it in action. See the GitHub repo for more information. GuiB, giovanni and xanimus 3 Link to comment
GuiB Posted September 9, 2017 Share Posted September 9, 2017 Thanks @deanishe, I'll have a look and try this! Thanks for your work Link to comment
dfay Posted January 13, 2018 Share Posted January 13, 2018 Thanks again for this. In case others need this info -- you can keep the fuzzy searching without sorting the results by deleting (or commenting out) the line items.sort(reverse=True) Link to comment
dfay Posted January 13, 2018 Share Posted January 13, 2018 (edited) I've run into a hitch. I want to use this to search a list of categories, then again within the same workflow to search elements within a category. But the second run is always displaying the cached results of the first. I tried duplicating the script and running each separately but it seems to still use the same session_id and hence display the cached results instead of the results of the second run. Any quick & easy ways to reset the session ID? I suppose I could duplicate it & change the os.getpid() to something else. UPDATE: I ungraciously and crudely solved the problem by 1. duplicating the file, 2. editing the duplicate copy a. changing line 45 to SID = 'fuzzy_session_id_2' b. changing the end of line 311 to str(os.getpid())+"A" I call the original in the first run and the modified duplicate in the second. I'm sure it could be done more gracefully/elegantly/pythonically but that approach works for me. Edited January 13, 2018 by dfay Link to comment
deanishe Posted January 13, 2018 Author Share Posted January 13, 2018 8 minutes ago, dfay said: Any quick & easy ways to reset the session ID? 8 minutes ago, dfay said: I suppose I could duplicate it & change the os.getpid() to something else Wouldn't work. That's only used to set the session ID when none exists. The problem is that both Script Filters are reading the session ID from the same variable (and therefore using the same cache). I've added a new session_var option. Put export session_var=filter2 (or some such) in your downstream Script Filter, so it stores its session ID in a different variable to the first one (and therefore uses a different cache). 1 hour ago, dfay said: In case others need this info -- you can keep the fuzzy searching without sorting the results by deleting (or commenting out) the line items.sort(reverse=True) Hmm. If you only want match/no match, without the ranking, you can do that 10x more quickly with re: pat = '.*' + '.*'.join(query) + '.*' match = re.compile(pat, re.I).match Link to comment
giovanni Posted March 14, 2021 Share Posted March 14, 2021 Is this still current? I tried it on a simple script returning a JSON list of names and got the following error: File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/json/decoder.py", line 382, in raw_decode raise ValueError("No JSON object could be decoded") ValueError: No JSON object could be decoded my input is 'as {query}' and I have tried both export query="$1" ./fuzzy.py python myScript.py and export query="{query}" ./fuzzy.py /usr/bin/python myScript.py 🙏 thanks! Link to comment
deanishe Posted March 14, 2021 Author Share Posted March 14, 2021 It doesn't like the output of your script, so I can't possibly say what's wrong without seeing your script and its output… giovanni 1 Link to comment
giovanni Posted March 14, 2021 Share Posted March 14, 2021 very simple, it's one of yours modified some of the items have emojis, perhaps that's the problem? It works well with the 'Alfred filters results' option, but I wanted to add fuzzy filtering. #!/usr/bin/env python # encoding: utf-8 # # Copyright (c) 2014 deanishe@deanishe.net # # MIT Licence. See http://opensource.org/licenses/MIT # # Created on 2014-07-03 # Modified from books, to show folders for filtering """Workflow Script Filter to show search results in Alfred.""" from __future__ import print_function, unicode_literals import sys import csv import os import struct from time import time from workflow import Workflow, ICON_INFO, ICON_WARNING from workflow.background import run_in_background, is_running from config import INDEX_DB log = None def main(wf): mylabels=[] with open("FOLDERS_ALL.tsv", "r") as fp: reader = csv.reader(fp, delimiter=b'\t') for row in reader: mylabel, myCount = [v.decode('utf-8') for v in row] toShow = mylabel #log.info (mylabel) wf.add_item(toShow, myCount + " papers", valid=True, arg=mylabel, icon='icon_folder.png') wf.send_feedback() if __name__ == '__main__': wf = Workflow() log = wf.logger sys.exit(wf.run(main)) Link to comment
deanishe Posted March 15, 2021 Author Share Posted March 15, 2021 I can't run that script without the rest of the files. Please upload your workflow somewhere and post a link. Link to comment
giovanni Posted March 15, 2021 Share Posted March 15, 2021 will do! just to clarify, fuzzy.py is the only script I am supposed to copy into my own workflow's folder, or are there others? Link to comment
deanishe Posted March 15, 2021 Author Share Posted March 15, 2021 fuzzy.py is standalone. It's a question of the other files your script requires. giovanni 1 Link to comment
giovanni Posted March 15, 2021 Share Posted March 15, 2021 here you go! keyword: zzl thank you!! https://github.com/giovannicoppola/paperpAlfred/blob/main/paperpAlfred troubleshooting.alfredworkflow Link to comment
deanishe Posted March 15, 2021 Author Share Posted March 15, 2021 It isn’t working because your script doesn’t output JSON. If you want to use Alfred-Workflow (which is probably unnecessary here) with fuzzy.py, you need to use the Workflow3 class to generate JSON. You’re using Workflow, which outputs XML. giovanni 1 Link to comment
giovanni Posted March 15, 2021 Share Posted March 15, 2021 awesome, thank you so much! I will try to figure it out. Link to comment
giovanni Posted March 15, 2021 Share Posted March 15, 2021 thanks again for your feedback @deanishe. I saved the dictionary as JSON instead of CSV and then read it in, which allowed me to ✅ avoid using Alfred-Workflow and ✅ be able to use fuzzy.pl. thanks!! dfay 1 Link to comment
marck Posted January 28, 2022 Share Posted January 28, 2022 Hey @deanishe, When I carbon-copy your Fuzzy demo, it won't work some reason, outputting Quote Code 1: [fuzzy] . [fuzzy] cmd=['cat', 'books.json'], query='', session_id=None Traceback (most recent call last): File "./fuzzy.py", line 377, in <module> main() File "./fuzzy.py", line 363, in main cache = Cache(cmd) File "./fuzzy.py", line 279, in __init__ self.cache_dir = os.path.join(CACHEDIR, '_fuzzy') File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/posixpath.py", line 70, in join elif path == '' or path.endswith('/'): AttributeError: 'NoneType' object has no attribute 'endswith' Am I missing something here? Do I need to install a local package or something? Thanks for your help, Marc Link to comment
marck Posted February 1, 2022 Share Posted February 1, 2022 Answering my own Q: The bundle ID must be set in order for the cache to work. Duplicating a workflow – obviously? – nullifies the bundle id. Link to comment
andy4222 Posted March 25, 2022 Share Posted March 25, 2022 @deanishe The fuzzy script does not seem to be working after upgrading to 12.3. This is a really important script since it powers multiple workflows for me. Anything I can do to make it work? I tried changing /usr/bin/python to /usr/bin/python3 but that didn't help. Other errors came up which I could not fix: error: raceback (most recent call last): File "./fuzzy.py", line 377, in <module> main() File "./fuzzy.py", line 364, in main fb = cache.load() File "./fuzzy.py", line 312, in load json.dump(fb, fp) File "/Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework/Versions/3.8/lib/python3.8/json/__init__.py", line 180, in dump fp.write(chunk) TypeError: a bytes-like object is required, not 'str' Link to comment
andy4222 Posted March 31, 2022 Share Posted March 31, 2022 @deanishe buzzing again if migrating this script to python 3 would be possible? Link to comment
dfay Posted March 31, 2022 Share Posted March 31, 2022 I've got a Python 3 version working & just posted it in @deanishe's github repo at https://github.com/deanishe/alfred-fuzzy/issues/3 andy4222 1 Link to comment
Recommended Posts
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now