Coverage for src/pyselector/menus/rofi.py: 100%
80 statements
« prev ^ index » next coverage.py v7.2.2, created at 2023-03-24 11:53 -0300
« prev ^ index » next coverage.py v7.2.2, created at 2023-03-24 11:53 -0300
1# menus.rofi.py
3import logging
4import shlex
5import sys
6import warnings
7from typing import Iterable
8from typing import Optional
9from typing import Union
11from pyselector import helpers
12from pyselector.interfaces import KeyManager
13from pyselector.interfaces import PromptReturn
15log = logging.getLogger(__name__)
17ROFI_RETURN_CODE_START = 10
18BULLET = "\u2022"
21class Rofi:
22 """
23 A Python wrapper for the rofi application, which provides a simple and
24 efficient way to display a list of items for user selection.
26 This class provides a convenient interface for building and executing rofi commands,
27 allowing customization of various settings such as case sensitivity, multi-select,
28 prompt message, and more
30 Methods:
31 prompt(items=None, case_sensitive=False, multi_select=False, prompt="PySelector> ", **kwargs):
32 Displays a rofi selection window with the specified items and settings,
33 returns the selected item(s) and return code.
34 """
36 def __init__(self) -> None:
37 self.name = "rofi"
38 self.url = "https://github.com/davatorium/rofi"
39 self.keybind = KeyManager()
41 self.keybind.code_count = ROFI_RETURN_CODE_START
43 @property
44 def command(self) -> str:
45 return helpers.check_command(self.name, self.url)
47 def _build_command(
48 self,
49 case_sensitive,
50 multi_select,
51 prompt,
52 **kwargs,
53 ) -> list[str]:
54 messages: list[str] = []
55 dimensions_args: list[str] = []
56 args = []
58 args.extend(shlex.split(self.command))
59 args.append("-dmenu")
61 if kwargs.get("theme"):
62 args.extend(["-theme", kwargs.pop("theme")])
64 if kwargs.get("lines"):
65 args.extend(["-l", str(kwargs.pop("lines"))])
67 if prompt:
68 args.extend(["-p", prompt])
70 if kwargs.get("mesg"):
71 messages.extend(shlex.split(f"'{kwargs.pop('mesg')}'"))
73 if kwargs.get("filter"):
74 args.extend(["-filter", kwargs.pop("filter")])
76 if kwargs.get("location"):
77 direction = kwargs.pop("location")
78 args.extend(["-location", self.location(direction)])
80 if kwargs.get("width"):
81 dimensions_args.append(f"width: {kwargs.pop('width')};")
83 if kwargs.get("height"):
84 dimensions_args.append(f"height: {kwargs.pop('height')};")
86 if case_sensitive:
87 args.append("-case-sensitive")
88 else:
89 args.append("-i")
91 if multi_select:
92 args.append("-multi-select")
94 if dimensions_args:
95 formated_string = " ".join(dimensions_args)
96 args.extend(shlex.split("-theme-str 'window {" + formated_string + "}'"))
98 for key in self.keybind.list_registered:
99 args.extend(shlex.split(f"-kb-custom-{key.id} {key.bind}"))
100 if not key.hidden:
101 messages.append(f"{BULLET} Use <{key.bind}> {key.description}")
103 if messages:
104 mesg = "\n".join(messages)
105 args.extend(shlex.split(f"-mesg '{mesg}'"))
107 if kwargs:
108 for arg, value in kwargs.items():
109 warnings.warn(UserWarning(f"'{arg}={value}' not supported"))
111 args.extend(shlex.split("-theme-str 'textbox { markup: false;}'"))
112 return args
114 def prompt(
115 self,
116 items: Optional[Iterable[Union[str, int]]] = None,
117 case_sensitive: bool = False,
118 multi_select: bool = False,
119 prompt: str = "PySelector> ",
120 **kwargs,
121 ) -> PromptReturn:
122 """Prompts the user with a rofi window containing the given items
123 and returns the selected item and code.
125 Args:
126 items (Iterable[str, int], optional): The items to display in the rofi window
127 case_sensitive (bool, optional): Whether or not to perform a case-sensitive search
128 multi_select (bool, optional): Whether or not to allow the user to select multiple items
129 prompt (str, optional): The prompt to display in the rofi window
130 **kwargs: Additional keyword arguments.
132 Keyword Args:
133 lines (int): The number of lines to display in the selection window.
134 mesg (str): A message to display at the top of the selection window.
135 filter (str): Filter the list by setting text in input bar to filter.
136 location (str): The location of the selection window (e.g. "upper-left", "center" or "bottom-right").
137 width (str): The width of the selection window (e.g. 60%).
138 height (str): The height of the selection window (e.g. 50%).
139 theme (str): The path of the rofi theme to use.
141 Returns:
142 A tuple containing the selected item (str or list of str if `multi_select` activated) and the return code (int).
143 """
144 if items is None:
145 items = []
147 args = self._build_command(case_sensitive, multi_select, prompt, **kwargs)
148 selection, code = helpers._execute(args, items)
150 if multi_select:
151 return helpers.parse_multiple_bytes_lines(selection), code
152 return helpers.parse_bytes_line(selection), code
154 @staticmethod
155 def location(direction: Optional[str] = None) -> str:
156 """
157 Specify where the window should be located. The numbers map to the
158 following locations on screen:
160 1 2 3
161 8 0 4
162 7 6 5
164 Default: 0
165 """
166 try:
167 location = {
168 "upper-left": 1,
169 "left": 8,
170 "bottom-left": 7,
171 "upper-center": 2,
172 "center": 0,
173 "bottom-center": 6,
174 "upper-right": 3,
175 "right": 4,
176 "bottom-right": 5,
177 }
178 return str(location[direction])
179 except KeyError as e:
180 raise KeyError(
181 "location %s not found.\nchosse from %s", e, list(location.keys())
182 ) from e
183 sys.exit(1)