Contents Menu Expand Light mode Dark mode Auto light/dark mode
zelfred 0.1.5 documentation
Logo
zelfred 0.1.5 documentation
  • What is This
  • App Gallery
  • UI Keyboard Shortcut
  • UI Event Loop
Back to top
Edit this page

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

https://asciinema.org/a/617869.svg

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

https://asciinema.org/a/617871.svg

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

https://asciinema.org/a/617874.svg

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

https://asciinema.org/a/616014.svg

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

https://asciinema.org/a/617801.svg

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

https://asciinema.org/a/616119.svg

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

https://asciinema.org/a/617807.svg

Source Code

Next
UI Keyboard Shortcut
Previous
What is This
Copyright © 2023, Sanhe Hu
Made with Sphinx and @pradyunsg's Furo
On this page
  • App Gallery
    • Random Password Generator
    • Calculate File Checksum
    • Select Item Using Fuzzy Match
    • Google Search with Suggestion
    • Search Google Chrome Bookmark
    • Folder and File Search
    • Password Book App