App Gallery#
Random Password Generator#
Difficulty: Easy
The user enters the length of the password, and the UI generates a few random passwords for the user to choose from. The user can tap “Enter” to copy the selected password to the clipboard.
Demo
Source Code
1# -*- coding: utf-8 -*-
2
3"""
4Feature:
5
6The user enters the length of the password, and the UI generates a few random passwords
7for the user to choose from. The user can tap "Enter" to copy the selected password
8to the clipboard, and the UI will exit.
9
10Difficulty: Easy
11
12Dependencies:
13
14.. code-block:: bash
15
16 pip install pyperclip>=1.8.0,<2.0.0
17
18Demo: https://asciinema.org/a/617869
19"""
20
21import typing as T
22import string
23import random
24import dataclasses
25
26# we need this library to copy text to clipboard
27import pyperclip
28
29# import zelfred public API
30import zelfred.api as zf
31
32
33# implement the random password generator
34def remove_chars(chars: str, to_remove: str) -> str:
35 for c in to_remove:
36 chars = chars.replace(c, "")
37 return chars
38
39
40to_remove = "iIlLoO1O" # we don't use these chars because they are easily confused
41
42charset_lower = remove_chars(string.ascii_lowercase, to_remove)
43charset_upper = remove_chars(string.ascii_uppercase, to_remove)
44charset_digits = remove_chars(string.digits, to_remove)
45charset_symbol = "!@#$%^&*()_+"
46charset = charset_lower + charset_upper + charset_digits + charset_symbol
47
48
49def random_password(length: int) -> str:
50 # first char must be a letter
51 first = random.choice(charset_lower + charset_upper)
52 # must have at least 1-2 digits, 1-2 uppercase, 1-2 lowercase, 1-2 symbol
53 digits = random.choices(charset_digits, k=random.randint(1, 2))
54 upper = random.choices(charset_upper, k=random.randint(1, 2))
55 lower = random.choices(charset_lower, k=random.randint(1, 2))
56 symbol = random.choices(charset_symbol, k=random.randint(1, 2))
57 # the rest can be anything
58 k = length - 1 - len(digits) - len(upper) - len(lower) - len(symbol)
59 if k:
60 rest = random.choices(charset, k=k)
61 else:
62 rest = []
63 tail = digits + upper + lower + symbol + rest
64 random.shuffle(tail)
65 return first + "".join(tail)
66
67
68@dataclasses.dataclass
69class Item(zf.Item):
70 """
71 The default ``zf.Item`` does not implement any user action handler methods.
72 To copy the password to the clipboard when the user taps "Enter," we need to
73 define a custom item class and override the ``enter_handler`` method."
74
75 By default, the UI can perform a "user action" when the following keys are tapped:
76
77 - Enter
78 - Ctrl A
79 - Ctrl W
80 - Ctrl U
81 - Ctrl P
82
83 When user taps one of these keys, the UI will call the corresponding handler method
84 and exit immediately.
85 """
86
87 def enter_handler(self, ui: zf.UI):
88 """
89 Copy the content to clipboard.
90 """
91 pyperclip.copy(self.arg)
92
93
94def handler(query: str, ui: zf.UI) -> T.List[Item]:
95 """
96 The handler is the core of a Zelfred App. It's a user-defined function
97 that takes the entered query and the UI object as inputs and returns
98 a list of items to render.
99 """
100 # if query is empty, display helper text
101 if bool(query) is False:
102 return [
103 Item(
104 title="Enter the length of the password ...",
105 subtitle="for example: 12",
106 # user can tap "Tab" to autocomplete the query
107 # we would like to generate a password with length 12 in most of the case
108 # so we set the autocomplete to 12
109 autocomplete="12",
110 )
111 ]
112 else:
113 # try to convert the query into an integer
114 try:
115 length = int(query)
116 # if the length is less than 10, display helper text
117 if length < 10:
118 return [
119 Item(
120 title=f"We don't support password length less than 10.",
121 subtitle="please enter a number greater than 10!",
122 )
123 ]
124 # if the length is valid, then generate some passwords
125 else:
126 items = list()
127 for _ in range(20):
128 pwd = random_password(length)
129 item = Item(
130 # the first line of the item in the UI, usually the content of the item.
131 # the ui.terminal is a blessed.Terminal object,
132 # we can use it to add syntax highlight to the text.
133 # you can find more information at https://blessed.readthedocs.io/en/latest/colors.html
134 title=f"{ui.terminal.cyan}{pwd}{ui.terminal.normal}",
135 # the second line of the item in the UI, usually some helper text.
136 subtitle=f"tap {ui.terminal.magenta}Enter{ui.terminal.normal} to copy this password to clipboard",
137 # the argument of the item will be used to copy to clipboard
138 arg=pwd,
139 )
140 items.append(item)
141 return items
142 # if the query is not a valid integer, display helper text
143 except ValueError:
144 return [
145 Item(
146 title=f"{query!r} is not a valid length.",
147 subtitle="please enter an integer!",
148 )
149 ]
150
151
152if __name__ == "__main__":
153 # reset the debugger and enable it
154 zf.debugger.reset()
155 zf.debugger.enable()
156
157 # create the UI and run it
158 ui = zf.UI(
159 # tell the UI to use the handler function
160 handler=handler,
161 # capture error and display debug information in the UI,
162 # this is the default behavior, if this is set to False, then it will
163 # raise Exception and stop the program.
164 capture_error=True,
165 )
166 # run the UI
167 ui.run()
Calculate File Checksum#
Difficulty: Easy
The user enters (or paste) the absolute path of the file, and the UI generates a few checksum algorithm options, then user can choose one and hit “Enter” to copy the checksum value of the selected algorithm. The UI will stay and user can continue to choose another algorithm and hit “Enter” again.
Demo
Source Code
1# -*- coding: utf-8 -*-
2
3"""
4Feature:
5
6The user enters (or paste) the absolute path of the file, and the UI generates
7a few checksum algorithm options, then user can choose one and hit "Enter" to
8copy the checksum value of the selected algorithm. The UI will stay and user can
9continue to choose another algorithm and hit "Enter" again.
10
11Difficulty: Easy
12
13Dependencies:
14
15.. code-block:: bash
16
17 pip install pyperclip>=1.8.0,<2.0.0
18
19Demo: https://asciinema.org/a/617871
20"""
21
22import typing as T
23import hashlib
24import dataclasses
25from pathlib import Path
26
27# we need this library to copy text to clipboard
28import pyperclip
29
30# import zelfred public API
31import zelfred.api as zf
32
33
34# implement the checksum calculator
35def md5_of_file(path: Path) -> str:
36 return hashlib.md5(path.read_bytes()).hexdigest()
37
38
39def sha1_of_file(path: Path) -> str:
40 return hashlib.sha1(path.read_bytes()).hexdigest()
41
42
43def sha256_of_file(path: Path) -> str:
44 return hashlib.sha256(path.read_bytes()).hexdigest()
45
46
47def sha512_of_file(path: Path) -> str:
48 return hashlib.sha512(path.read_bytes()).hexdigest()
49
50
51algorithms = [
52 "md5",
53 "sha1",
54 "sha256",
55 "sha512",
56]
57
58algorithm_to_function_mapper = {
59 "md5": md5_of_file,
60 "sha1": sha1_of_file,
61 "sha256": sha256_of_file,
62 "sha512": sha512_of_file,
63}
64
65
66@dataclasses.dataclass
67class Item(zf.Item):
68 """
69 The default ``zf.Item`` does not implement any user action handler methods.
70 To copy the password to the clipboard when the user taps "Enter," we need to
71 define a custom item class and override the ``enter_handler`` method."
72
73 By default, the UI can perform a "user action" when the following keys are tapped:
74
75 - Enter
76 - Ctrl A
77 - Ctrl W
78 - Ctrl U
79 - Ctrl P
80
81 When the user taps one of these keys, the UI calls the corresponding
82 handler method and exits immediately. To modify this behavior, we can
83 override the ``post_enter_handler`` method, which is invoked after the
84 ``enter_handler`` method."
85 """
86
87 def enter_handler(self, ui: zf.UI):
88 """
89 Copy the checksum of the file to clipboard.
90
91 The path of the file is stored in the ``self.variables["path"]``.
92 The checksum algorithm is stored in the ``self.variables["algo"]``.
93 """
94 path = Path(self.variables["path"])
95 algo = self.variables["algo"]
96 checksum = algorithm_to_function_mapper[algo](path)
97 pyperclip.copy(checksum)
98
99 def post_enter_handler(self, ui: zf.UI):
100 """
101 We would like to keep the UI displayed after the user hits 'Enter'.
102 Typically, any user input change will trigger the UI to re-render
103 and then wait for the next user input. The 'UI.wait_next_user_input()'
104 method allows you to skip the re-rendering and wait for the next user input."
105 """
106 ui.wait_next_user_input()
107
108
109def handler(query: str, ui: zf.UI) -> T.List[Item]:
110 """
111 The handler is the core of a Zelfred App. It's a user-defined function
112 that takes the entered query and the UI object as inputs and returns
113 a list of items to render.
114 """
115 # if query is empty, display helper text
116 if bool(query) is False:
117 return [
118 Item(
119 title="Enter the absolute path of the file ...",
120 subtitle="for example: ${HOME}/.zshrc",
121 # since my demo is running on macOS, I use the ${HOME}/.zshrc file
122 # as an example, you can change it to any file you want.
123 autocomplete=str(Path.home().joinpath(".zshrc")),
124 )
125 ]
126 else:
127 path = Path(query)
128 # if the path does not exist, user might be still typing,
129 # so we just display a helper text
130 if path.exists() is False:
131 return [
132 Item(
133 title="Keep entering ...",
134 subtitle=f"{path} doesn't exists.",
135 )
136 ]
137 # if the path is a directory, we display a helper text
138 elif path.is_dir():
139 return [
140 Item(
141 title="🔴 We cannot calculate checksum for a directory!",
142 subtitle=f"{path} is a directory.",
143 )
144 ]
145 # if the path is a file, we display the checksum options
146 elif path.is_file():
147 return [
148 Item(
149 title=f"{algo} of {path}",
150 # the ui.terminal is a blessed.Terminal object,
151 # we can use it to add syntax highlight to the text.
152 # you can find more information at https://blessed.readthedocs.io/en/latest/colors.html
153 subtitle=f"tap {ui.terminal.magenta}Enter{ui.terminal.normal} to copy this checksum to clipboard.",
154 variables={
155 "path": f"{path}",
156 "algo": algo,
157 },
158 )
159 for algo in algorithms
160 ]
161 else: # this should never happen, but just in case
162 raise NotImplementedError
163
164
165if __name__ == "__main__":
166 # reset the debugger and enable it
167 zf.debugger.reset()
168 zf.debugger.enable()
169
170 # create the UI and run it
171 ui = zf.UI(
172 # tell the UI to use the handler function
173 handler=handler,
174 # capture error and display debug information in the UI,
175 # this is the default behavior, if this is set to False, then it will
176 # raise Exception and stop the program.
177 capture_error=True,
178 )
179 # run the UI
180 ui.run()
Select Item Using Fuzzy Match#
Difficulty: Easy
Use the user input to sort a list of items by fuzzy match similarity. Allow user to tap “Enter” to copy the content to clipboard.
Demo
Source Code
1# -*- coding: utf-8 -*-
2
3"""
4Feature:
5
6Use the user input to sort a list of items by fuzzy match similarity.
7Allow user to tap "Enter" to copy the content to clipboard.
8
9Dependencies:
10
11.. code-block:: bash
12
13 pip install fuzzywuzzy>=0.18.0,<1.0.0
14 pip install python-Levenshtein>=0.21.0,<1.0.0
15 pip install pyperclip>=1.8.0,<2.0.0
16
17Demo: https://asciinema.org/a/617874
18"""
19
20import dataclasses
21
22# we need this library to copy text to clipboard
23import pyperclip
24
25# we need this library to do fuzzy match
26from fuzzywuzzy import process
27
28# import zelfred public API
29import zelfred.api as zf
30
31
32@dataclasses.dataclass
33class Item(zf.Item):
34 """
35 The default ``zf.Item`` does not implement any user action handler methods.
36 To copy the password to the clipboard when the user taps "Enter," we need to
37 define a custom item class and override the ``enter_handler`` method."
38
39 By default, the UI can perform a "user action" when the following keys are tapped:
40
41 - Enter
42 - Ctrl A
43 - Ctrl W
44 - Ctrl U
45 - Ctrl P
46
47 When user taps one of these keys, the UI will call the corresponding handler method
48 and exit immediately.
49 """
50
51 def enter_handler(self, ui: zf.UI):
52 """
53 Copy the content to clipboard.
54 """
55 pyperclip.copy(self.arg)
56
57
58def create_item(text: str, ui: zf.UI) -> Item:
59 """
60 A helper function to create an item from text.
61 """
62 return Item(
63 title=text,
64 # the ui.terminal is a blessed.Terminal object,
65 # we can use it to add syntax highlight to the text.
66 # you can find more information at https://blessed.readthedocs.io/en/latest/colors.html
67 subtitle=f"hit {ui.terminal.magenta}Enter{ui.terminal.normal} to copy to clipboard",
68 # uid is a unique identifier of the item for internal deduplication
69 # if you don't specify it, it will be generated automatically
70 uid=text,
71 # user can tap "Tab" to autocomplete the query
72 autocomplete=text,
73 # the argument of the item will be used to copy to clipboard
74 arg=text,
75 )
76
77
78zen_of_python = [
79 "Beautiful is better than ugly.",
80 "Explicit is better than implicit.",
81 "Simple is better than complex.",
82 "Complex is better than complicated.",
83 "Flat is better than nested.",
84 "Sparse is better than dense.",
85 "Readability counts.",
86 "Special cases aren't special enough to break the rules.",
87 "Although practicality beats purity.",
88 "Errors should never pass silently.",
89 "Unless explicitly silenced.",
90 "In the face of ambiguity, refuse the temptation to guess.",
91 "There should be one-- and preferably only one --obvious way to do it.",
92 "Although that way may not be obvious at first unless you're Dutch.",
93 "Now is better than never.",
94 "Although never is often better than *right* now.",
95 "If the implementation is hard to explain, it's a bad idea.",
96 "If the implementation is easy to explain, it may be a good idea.",
97 "Namespaces are one honking great idea -- let's do more of those!",
98]
99
100
101def handler(query: str, ui: zf.UI):
102 """
103 The handler is the core of a Zelfred App. It's a user-defined function
104 that takes the entered query and the UI object as inputs and returns
105 a list of items to render.
106 """
107 # if query is not empty
108 if query:
109 # sort by fuzzy match similarity
110 # you can find more information at: https://github.com/seatgeek/fuzzywuzzy
111 results = process.extract(query, zen_of_python, limit=len(zen_of_python))
112 return [create_item(text, ui) for text, score in results]
113 # if query is empty, return the full list in the original order
114 else:
115 return [create_item(text, ui) for text in zen_of_python]
116
117
118if __name__ == "__main__":
119 # reset the debugger and enable it
120 zf.debugger.reset()
121 zf.debugger.enable()
122
123 # create the UI and run it
124 ui = zf.UI(
125 # tell the UI to use the handler function
126 handler=handler,
127 # capture error and display debug information in the UI,
128 # this is the default behavior, if this is set to False, then it will
129 # raise Exception and stop the program.
130 capture_error=True,
131 )
132 # run the UI
133 ui.run()
Google Search with Suggestion#
Difficulty: Medium
The user types a query and receives a dropdown list of Google search suggestions. The user can then tap “Enter” to perform a Google search in their web browser.
Demo
Source Code
1# -*- coding: utf-8 -*-
2
3"""
4Feature:
5
6The user types a query and receives a dropdown list of Google search suggestions.
7The user can then tap "Enter" to perform a Google search in their web browser.
8
9Difficulty: Medium
10
11Dependencies:
12
13.. code-block:: bash
14
15 pip install requests
16
17Demo: https://asciinema.org/a/616014
18"""
19
20import typing as T
21import dataclasses
22
23# we need this to parse google search API response
24import xml.etree.ElementTree as ET
25
26# we need this to send HTTP request
27import requests
28import zelfred.api as zf
29
30
31@dataclasses.dataclass
32class Item(zf.Item):
33 def enter_handler(self, ui: zf.UI):
34 """
35 Open the url in default web browser.
36 """
37 zf.open_url(self.arg)
38
39
40def encode_query(query: str) -> str:
41 """
42 Encode the query to be used in the url.
43 """
44 return query.replace(" ", "+")
45
46
47class GoogleComplete:
48 """
49 Google complete API caller and parser.
50 """
51
52 google_complete_endpoint = (
53 "https://www.google.com/complete/search?output=toolbar&q={query}"
54 )
55
56 def _encode_endpoint(self, query: str) -> str:
57 """
58 :return: full api url.
59 """
60 query = "+".join([s for s in query.split(" ") if s.strip()])
61 return self.google_complete_endpoint.format(query=query)
62
63 def _parse_response(self, html: str) -> T.List[str]:
64 """
65 :return: list of suggestions.
66 """
67 root = ET.fromstring(html)
68 suggestion_list = list()
69 for suggestion in root.iter("suggestion"):
70 suggestion_list.append(suggestion.attrib["data"])
71 return suggestion_list
72
73 def get(self, query: str) -> T.List[str]:
74 """
75 :return: list of suggestions.
76 """
77 url = self._encode_endpoint(query)
78 html = requests.get(url).text
79 suggestion_list = self._parse_response(html)
80 return suggestion_list
81
82
83google_complete = GoogleComplete()
84
85
86def handler(query: str, ui: zf.UI):
87 """
88 The handler is the core of a Zelfred App. It's a user-defined function
89 that takes the entered query and the UI object as inputs and returns
90 a list of items to render.
91 """
92 # if query is empty, return the full list in the original order
93 if bool(query) is False:
94 return [
95 Item(
96 title="type something to search in google",
97 )
98 ]
99 # if query is not empty
100 else:
101 suggestion_list = google_complete.get(query)
102 return [
103 Item(
104 title=s,
105 subtitle=f"hit 'Enter' to Google search {s!r} in web browser",
106 uid=s,
107 autocomplete=s,
108 # store google search url in arg, so we can access it in enter_handler
109 arg=f"https://www.google.com/search?q={encode_query(s)}",
110 )
111 for s in suggestion_list
112 ]
113
114
115if __name__ == "__main__":
116 # reset the debugger and enable it
117 zf.debugger.reset()
118 zf.debugger.enable()
119
120 # create the UI and run it
121 ui = zf.UI(handler=handler, capture_error=True)
122 ui.run()
Search Google Chrome Bookmark#
Difficulty: Medium
User type query and return a dropdown list of matched Google Chrome bookmarks. User can tap “Enter” to open it in default web browser.
Demo
Source Code
1# -*- coding: utf-8 -*-
2
3"""
4Feature:
5
6User type query and return a dropdown list of matched Google Chrome bookmarks.
7User can tap "Enter" to open it in default web browser.
8
9Difficulty: Medium
10
11Dependencies:
12
13.. code-block:: bash
14
15 pip install sayt==0.6.3
16
17Demo: https://asciinema.org/a/617801
18"""
19
20import typing as T
21import json
22import dataclasses
23from pathlib import Path
24
25# we need this to index and search the data
26import sayt.api as sayt
27
28# import zelfred public API
29import zelfred.api as zf
30
31# define important file paths
32dir_home = Path.home()
33dir_zelfred = dir_home.joinpath(".zelfred")
34dir_root = dir_zelfred.joinpath("app-gallery", "search-google-chrome-bookmark")
35dir_root.mkdir(parents=True, exist_ok=True)
36dir_index = dir_root.joinpath(".index")
37dir_cache = dir_root.joinpath(".cache")
38
39
40def find_google_chrom_bookmark_file_on_windows() -> Path:
41 """
42 Locate the Google Chrome bookmark file on Windows.
43 """
44 path = dir_home.joinpath(
45 "AppData",
46 "Local",
47 "Google",
48 "Chrome",
49 "User Data",
50 "Default",
51 "Bookmarks",
52 )
53 if path.exists():
54 return path
55 else:
56 raise FileNotFoundError
57
58
59def find_google_chrome_bookmark_file_on_mac() -> Path:
60 """
61 Locate the Google Chrome bookmark file on MacOS.
62 """
63 path = dir_home.joinpath(
64 "Library",
65 "Application Support",
66 "Google",
67 "Chrome",
68 "Default",
69 "Bookmarks",
70 )
71 if path.exists():
72 return path
73 else:
74 raise FileNotFoundError
75
76
77def find_google_chrome_bookmark_file() -> Path:
78 """
79 Locate the Google Chrome bookmark file.
80
81 Reference: https://www.howtogeek.com/welcome-to-cybersecurity-awareness-week-2023/
82 """
83 for func in [
84 find_google_chrom_bookmark_file_on_windows,
85 find_google_chrome_bookmark_file_on_mac,
86 ]:
87 try:
88 return func()
89 except FileNotFoundError:
90 pass
91 raise FileNotFoundError
92
93
94@dataclasses.dataclass
95class Bookmark:
96 """
97 Google Chrome bookmark dataclass.
98 """
99
100 name: str
101 url: str
102 name_text: str
103 name_ngram: str
104
105
106def parse_bookmark_file(p: Path) -> T.List[Bookmark]:
107 """
108 extract list of bookmark object from the bookmark file.
109 """
110 data = json.loads(p.read_text())
111 bookmark_dct = data.get("roots", {}).get("bookmark_bar", {})
112
113 def extract_bookmark(
114 node: dict,
115 _bookmark_list: T.Optional[T.List[Bookmark]] = None,
116 ):
117 if _bookmark_list is None:
118 _bookmark_list = list()
119 for dct in node.get("children", []):
120 if "url" in dct:
121 name = dct["name"]
122 bookmark = Bookmark(
123 name=name,
124 name_text=name,
125 name_ngram=name,
126 url=dct["url"],
127 )
128 _bookmark_list.append(bookmark)
129 else:
130 extract_bookmark(dct, _bookmark_list)
131 return _bookmark_list
132
133 return extract_bookmark(node=bookmark_dct)
134
135
136# def downloader():
137# """
138# Return the list of bookmark object for search.
139# """
140# p = find_google_chrome_bookmark_file()
141# bookmark_list = parse_bookmark_file(p)
142# return [dataclasses.asdict(bm) for bm in bookmark_list]
143
144
145def downloader():
146 """
147 This function returns dummy data for demo.
148 """
149 data = [
150 ("Google", "https://www.google.com/"),
151 ("Facebook", "https://www.facebook.com/"),
152 ("Amazon", "https://www.amazon.com/"),
153 ("Apple", "https://www.apple.com/"),
154 ("Linkedin", "https://www.linkedin.com/"),
155 ("Microsoft", "https://www.microsoft.com/"),
156 ]
157 return [
158 dataclasses.asdict(
159 Bookmark(
160 name=name,
161 url=url,
162 name_text=name,
163 name_ngram=name,
164 )
165 )
166 for name, url in data
167 ]
168
169
170# create the search as you type dataset, it will automatically refresh every 5 minutes
171ds = sayt.DataSet(
172 dir_index=dir_index,
173 index_name="google_chrome_bookmark",
174 fields=[
175 sayt.TextField("name_text", stored=False),
176 sayt.NgramWordsField("name_ngram", minsize=2, maxsize=8),
177 sayt.StoredField("name"),
178 sayt.StoredField("url"),
179 ],
180 dir_cache=dir_cache,
181 cache_key="google_chrome_bookmark",
182 cache_tag="google_chrome_bookmark",
183 cache_expire=5 * 60,
184 downloader=downloader,
185)
186
187
188@dataclasses.dataclass
189class UrlItem(zf.Item):
190 """
191 Define the custom item class because we need to override the ``enter_handler``.
192 """
193
194 @classmethod
195 def from_doc(cls, doc: dict):
196 return cls(
197 title=doc["name"],
198 subtitle="hit 'Enter' to open: " + doc["url"],
199 uid=doc["url"],
200 autocomplete=doc["name"],
201 arg=doc["url"],
202 )
203
204 def enter_handler(self, ui: zf.UI):
205 """
206 Open the bookmark in web browser.
207 """
208 zf.open_url(self.arg)
209
210
211def handler(query: str, ui: zf.UI):
212 """
213 The handler is the core of a Zelfred App. It's a user-defined function
214 that takes the entered query and the UI object as inputs and returns
215 a list of items to render.
216 """
217 # if query is not empty
218 if query:
219 docs = ds.search(query)
220 # if query is empty, just list first 20 bookmarks
221 else:
222 docs = ds.search("*")
223
224 # if find matches
225 if len(docs):
226 return [UrlItem.from_doc(doc) for doc in docs]
227 # if no match
228 else:
229 return [
230 UrlItem(
231 title="No result found.",
232 subtitle="check your data",
233 uid="no-result",
234 )
235 ]
236
237
238if __name__ == "__main__":
239 # reset the debugger and enable it
240 zf.debugger.reset()
241 zf.debugger.enable()
242
243 # create the UI and run it
244 ui = zf.UI(handler=handler, capture_error=False)
245 ui.run()
Folder and File Search#
Difficulty: Hard
User can search folder in a root directory, and then tap “Enter” to enter a sub query session to search file in the selected folder. At the end, user can tab “Enter” to open the file using the default application. Also, user can tap “F1” to exit the sub query session and go back to the folder search session.
Demo
Source Code
1# -*- coding: utf-8 -*-
2
3"""
4Feature:
5
6User can search folder in a root directory, and then tap "Enter" to enter
7a sub query session to search file in the selected folder. At the end, user
8can tab "Enter" to open the file using the default application. Also, user can
9tap "F1" to exit the sub query session and go back to the folder search session.
10
11Difficulty: Hard
12
13Dependencies:
14
15.. code-block:: bash
16
17 pip install fuzzywuzzy>=0.18.0,<1.0.0
18 pip install python-Levenshtein>=0.21.0,<1.0.0
19
20Demo: https://asciinema.org/a/616119
21"""
22
23import typing as T
24import dataclasses
25from pathlib import Path
26
27from fuzzywuzzy import process
28import zelfred.api as zf
29
30
31@dataclasses.dataclass
32class FileItem(zf.Item):
33 """
34 Represent a file in the dropdown menu.
35 """
36
37 @classmethod
38 def from_names(cls, name_list: T.List[str], folder: str) -> T.List["FileItem"]:
39 """
40 Convert a file name list to a list of items. The file name
41 will become the title, uid, autocomplete and the arg.
42 """
43 return [
44 cls(
45 uid=name,
46 title=name,
47 subtitle=f"hit 'Enter' to open this file",
48 arg=str(dir_home.joinpath(folder, name)),
49 autocomplete=name,
50 )
51 for name in name_list
52 ]
53
54 def enter_handler(self, ui: zf.UI):
55 """
56 Open file in default application.
57 """
58 zf.open_file(Path(self.arg))
59
60
61@dataclasses.dataclass
62class FolderItem(zf.Item):
63 """
64 Represent a folder in the dropdown menu.
65 """
66
67 @classmethod
68 def from_names(cls, name_list: T.List[str]) -> T.List["FolderItem"]:
69 """
70 Convert a folder name list to a list of items. The folder name
71 will become the title, uid, autocomplete and the arg.
72 """
73 return [
74 cls(
75 title=name,
76 subtitle=f"hit 'Enter' to search file in this folder",
77 uid=name,
78 autocomplete=name, # allow user to tap 'Tab' to auto-complete
79 arg=name, # the argument of the folder item will be used to list files
80 )
81 for name in name_list
82 ]
83
84 def enter_handler(self, ui: zf.UI):
85 """
86 Enter a sub query session.
87 """
88 # define the new handler function for the sub query session
89 folder = self.arg
90
91 def sub_handler(query: str, ui: zf.UI):
92 """
93 A partial function that using the given folder.
94 """
95 return handler_file(folder, query, ui)
96
97 # run the sub session using the new handler
98 # user can tap 'F1' to exit the sub query session,
99 # and go back to the folder selection session.
100 ui.run_sub_session(handler=sub_handler, initial_query="")
101
102
103dir_home = Path(__file__).absolute().parent.joinpath("home")
104
105
106def handler_file(folder: str, query: str, ui: zf.UI):
107 """
108 This is the handler for the sub query session.
109
110 Given a folder, you have to create a partial function that using the given
111 folder. The partial function will become the final handler of the sub query.
112 """
113 file_list = [p.name for p in dir_home.joinpath(folder).iterdir()]
114 # if query is not empty
115 if query:
116 # sort by fuzzy match similarity
117 results = process.extract(query, file_list, limit=len(file_list))
118 return FileItem.from_names([file for file, score in results], folder)
119 # if query is empty, return the full list in the original order
120 else:
121 return FileItem.from_names(file_list, folder)
122
123
124def handler_folder(query: str, ui: zf.UI):
125 """
126 This is the handler for folder selection.
127 """
128 folder_list = [p.name for p in dir_home.iterdir()]
129 # if query is not empty
130 if query:
131 # sort by fuzzy match similarity
132 results = process.extract(query, folder_list, limit=len(folder_list))
133 return FolderItem.from_names([folder for folder, score in results])
134 # if query is empty, return the full list in the original order
135 else:
136 return FolderItem.from_names(folder_list)
137
138
139if __name__ == "__main__":
140 # reset the debugger and enable it
141 zf.debugger.reset()
142 zf.debugger.enable()
143
144 # create the UI and run it
145 ui = zf.UI(handler=handler_folder, capture_error=True)
146 ui.run()
Password Book App#
Difficulty: Hard
User the user input to search the username, allow user to tap “Ctrl A” to copy the password to clipboard. Afterward, the UI doesn’t exit and wait for the next user input.
Demo
Source Code