euporie 2.8.2__py3-none-any.whl → 2.8.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. euporie/console/tabs/console.py +227 -104
  2. euporie/core/__init__.py +1 -1
  3. euporie/core/__main__.py +1 -1
  4. euporie/core/app.py +31 -18
  5. euporie/core/clipboard.py +1 -1
  6. euporie/core/comm/ipywidgets.py +5 -5
  7. euporie/core/commands.py +1 -1
  8. euporie/core/config.py +4 -4
  9. euporie/core/convert/datum.py +4 -1
  10. euporie/core/convert/registry.py +7 -2
  11. euporie/core/filters.py +3 -1
  12. euporie/core/ft/html.py +2 -4
  13. euporie/core/graphics.py +6 -6
  14. euporie/core/kernel.py +56 -32
  15. euporie/core/key_binding/bindings/__init__.py +2 -1
  16. euporie/core/key_binding/bindings/mouse.py +24 -22
  17. euporie/core/key_binding/bindings/vi.py +46 -0
  18. euporie/core/layout/cache.py +33 -23
  19. euporie/core/layout/containers.py +235 -73
  20. euporie/core/layout/decor.py +3 -3
  21. euporie/core/layout/print.py +14 -2
  22. euporie/core/layout/scroll.py +15 -21
  23. euporie/core/margins.py +59 -30
  24. euporie/core/style.py +7 -5
  25. euporie/core/tabs/base.py +32 -0
  26. euporie/core/tabs/notebook.py +6 -3
  27. euporie/core/terminal.py +12 -17
  28. euporie/core/utils.py +2 -4
  29. euporie/core/widgets/cell.py +64 -109
  30. euporie/core/widgets/dialog.py +25 -20
  31. euporie/core/widgets/file_browser.py +3 -3
  32. euporie/core/widgets/forms.py +8 -7
  33. euporie/core/widgets/inputs.py +21 -9
  34. euporie/core/widgets/layout.py +5 -5
  35. euporie/core/widgets/status.py +3 -3
  36. euporie/hub/app.py +7 -3
  37. euporie/notebook/app.py +68 -47
  38. euporie/notebook/tabs/log.py +1 -1
  39. euporie/notebook/tabs/notebook.py +5 -3
  40. euporie/preview/app.py +3 -0
  41. euporie/preview/tabs/notebook.py +9 -14
  42. euporie/web/tabs/web.py +0 -1
  43. {euporie-2.8.2.dist-info → euporie-2.8.4.dist-info}/METADATA +5 -5
  44. {euporie-2.8.2.dist-info → euporie-2.8.4.dist-info}/RECORD +49 -48
  45. {euporie-2.8.2.data → euporie-2.8.4.data}/data/share/applications/euporie-console.desktop +0 -0
  46. {euporie-2.8.2.data → euporie-2.8.4.data}/data/share/applications/euporie-notebook.desktop +0 -0
  47. {euporie-2.8.2.dist-info → euporie-2.8.4.dist-info}/WHEEL +0 -0
  48. {euporie-2.8.2.dist-info → euporie-2.8.4.dist-info}/entry_points.txt +0 -0
  49. {euporie-2.8.2.dist-info → euporie-2.8.4.dist-info}/licenses/LICENSE +0 -0
@@ -49,7 +49,7 @@ if TYPE_CHECKING:
49
49
 
50
50
  from prompt_toolkit.buffer import Buffer
51
51
  from prompt_toolkit.completion.base import Completer
52
- from prompt_toolkit.formatted_text.base import OneStyleAndTextTuple
52
+ from prompt_toolkit.formatted_text.base import StyleAndTextTuples
53
53
 
54
54
  from euporie.core.format import Formatter
55
55
  from euporie.core.inspection import Inspector
@@ -234,36 +234,11 @@ class Cell:
234
234
  fill = partial(Window, style="class:border")
235
235
 
236
236
  # Create textbox for standard input
237
- def _send_input(buf: Buffer) -> bool:
238
- return False
239
-
240
237
  self.stdin_box = StdInput(weak_self.kernel_tab)
241
238
 
242
- top_border = VSplit(
243
- [
244
- self.control,
245
- ConditionalContainer(
246
- content=fill(
247
- char=border_char("TOP_MID"),
248
- width=lambda: len(weak_self.prompt),
249
- height=1,
250
- ),
251
- filter=show_prompt,
252
- ),
253
- ConditionalContainer(
254
- content=fill(width=1, height=1, char=border_char("TOP_SPLIT")),
255
- filter=show_prompt,
256
- ),
257
- fill(char=border_char("TOP_MID"), height=1),
258
- fill(width=1, height=1, char=border_char("TOP_RIGHT")),
259
- ],
260
- height=1,
261
- )
262
-
263
239
  input_row = ConditionalContainer(
264
240
  VSplit(
265
241
  [
266
- fill(width=1, char=border_char("MID_LEFT")),
267
242
  ConditionalContainer(
268
243
  content=Window(
269
244
  FormattedTextControl(
@@ -276,63 +251,44 @@ class Cell:
276
251
  ("", "\n ", lambda e: NotImplemented),
277
252
  ]
278
253
  ),
279
- width=lambda: len(weak_self.prompt),
254
+ width=lambda: len(weak_self.prompt) + 1,
280
255
  height=Dimension(preferred=1),
281
256
  style="class:input,prompt",
282
257
  ),
283
258
  filter=show_prompt,
284
259
  ),
285
- ConditionalContainer(
286
- fill(width=1, char=border_char("MID_SPLIT")),
287
- filter=show_prompt,
288
- ),
289
260
  ConditionalContainer(self.input_box, filter=~source_hidden),
290
261
  ConditionalContainer(
291
262
  Window(
292
263
  FormattedTextControl(
293
- [
294
- cast(
295
- "OneStyleAndTextTuple",
264
+ cast(
265
+ "StyleAndTextTuples",
266
+ [
267
+ (
268
+ "class:cell,show,inputs,border",
269
+ "▏",
270
+ on_click(self.show_input),
271
+ ),
296
272
  (
297
273
  "class:cell,show,inputs",
298
- " ",
274
+ "…",
299
275
  on_click(self.show_input),
300
276
  ),
301
- )
302
- ]
277
+ (
278
+ "class:cell,show,inputs,border",
279
+ "▕",
280
+ on_click(self.show_input),
281
+ ),
282
+ ],
283
+ )
303
284
  )
304
285
  ),
305
286
  filter=source_hidden,
306
287
  ),
307
- fill(width=1, char=border_char("MID_RIGHT")),
308
288
  ],
309
289
  ),
310
290
  filter=show_input,
311
291
  )
312
- middle_line = ConditionalContainer(
313
- content=VSplit(
314
- [
315
- fill(width=1, height=1, char=border_char("SPLIT_LEFT")),
316
- ConditionalContainer(
317
- content=fill(
318
- char=border_char("SPLIT_MID"),
319
- width=lambda: len(weak_self.prompt),
320
- ),
321
- filter=show_prompt,
322
- ),
323
- ConditionalContainer(
324
- content=fill(
325
- width=1, height=1, char=border_char("SPLIT_SPLIT")
326
- ),
327
- filter=show_prompt,
328
- ),
329
- fill(char=border_char("SPLIT_MID")),
330
- fill(width=1, height=1, char=border_char("SPLIT_RIGHT")),
331
- ],
332
- height=1,
333
- ),
334
- filter=(show_input & show_output) | self.stdin_box.visible,
335
- )
336
292
 
337
293
  outputs_hidden = Condition(
338
294
  lambda: weak_self.json["metadata"]
@@ -344,7 +300,6 @@ class Cell:
344
300
  output_row = ConditionalContainer(
345
301
  VSplit(
346
302
  [
347
- fill(width=1, char=border_char("MID_LEFT")),
348
303
  ConditionalContainer(
349
304
  content=Window(
350
305
  FormattedTextControl(
@@ -357,20 +312,12 @@ class Cell:
357
312
  ("", "\n ", lambda e: NotImplemented),
358
313
  ],
359
314
  ),
360
- width=lambda: len(weak_self.prompt),
315
+ width=lambda: len(weak_self.prompt) + 1,
361
316
  height=Dimension(preferred=1),
362
317
  style="class:output,prompt",
363
318
  ),
364
319
  filter=show_prompt,
365
320
  ),
366
- ConditionalContainer(
367
- content=fill(width=1, char=border_char("MID_SPLIT")),
368
- filter=show_prompt,
369
- ),
370
- ConditionalContainer(
371
- fill(width=1, char=border_char("MID_MID")),
372
- filter=~show_prompt,
373
- ),
374
321
  HSplit(
375
322
  [
376
323
  ConditionalContainer(
@@ -380,51 +327,38 @@ class Cell:
380
327
  ConditionalContainer(
381
328
  Window(
382
329
  FormattedTextControl(
383
- [
384
- cast(
385
- "OneStyleAndTextTuple",
330
+ cast(
331
+ "StyleAndTextTuples",
332
+ [
333
+ (
334
+ "class:cell,show,outputs,border",
335
+ "▏",
336
+ on_click(self.show_output),
337
+ ),
386
338
  (
387
339
  "class:cell,show,outputs",
388
- " ",
340
+ "…",
389
341
  on_click(self.show_output),
390
342
  ),
391
- ),
392
- ]
343
+ (
344
+ "class:cell,show,outputs,border",
345
+ "▕",
346
+ on_click(self.show_output),
347
+ ),
348
+ ],
349
+ )
393
350
  )
394
351
  ),
395
352
  filter=outputs_hidden,
396
353
  ),
397
354
  self.stdin_box,
398
- ]
399
- ),
400
- ConditionalContainer(
401
- fill(width=1, char=border_char("MID_MID")),
402
- filter=~show_prompt,
355
+ ],
403
356
  ),
404
- fill(width=1, char=border_char("MID_RIGHT")),
405
357
  ],
358
+ width=Dimension(min=1),
406
359
  ),
407
360
  filter=show_output | self.stdin_box.visible,
408
361
  )
409
- bottom_border = VSplit(
410
- [
411
- fill(width=1, height=1, char=border_char("BOTTOM_LEFT")),
412
- ConditionalContainer(
413
- content=fill(
414
- char=border_char("BOTTOM_MID"),
415
- width=lambda: len(weak_self.prompt),
416
- ),
417
- filter=show_prompt,
418
- ),
419
- ConditionalContainer(
420
- content=fill(width=1, height=1, char=border_char("BOTTOM_SPLIT")),
421
- filter=show_prompt,
422
- ),
423
- fill(char=border_char("BOTTOM_MID")),
424
- fill(width=1, height=1, char=border_char("BOTTOM_RIGHT")),
425
- ],
426
- height=1,
427
- )
428
362
 
429
363
  def _style() -> str:
430
364
  """Calculate the cell's style given its state."""
@@ -438,11 +372,32 @@ class Cell:
438
372
 
439
373
  self.container = HSplit(
440
374
  [
441
- top_border,
442
- input_row,
443
- middle_line,
444
- output_row,
445
- bottom_border,
375
+ VSplit(
376
+ [
377
+ self.control,
378
+ fill(char=border_char("TOP_MID"), height=1),
379
+ fill(width=1, height=1, char=border_char("TOP_RIGHT")),
380
+ ],
381
+ height=1,
382
+ ),
383
+ VSplit(
384
+ [
385
+ fill(width=1, char=border_char("MID_LEFT")),
386
+ HSplit(
387
+ [input_row, output_row],
388
+ padding=lambda: 1 if show_input() and show_output() else 0,
389
+ ),
390
+ fill(width=1, char=border_char("MID_RIGHT")),
391
+ ]
392
+ ),
393
+ VSplit(
394
+ [
395
+ fill(width=1, height=1, char=border_char("BOTTOM_LEFT")),
396
+ fill(char=border_char("BOTTOM_MID")),
397
+ fill(width=1, height=1, char=border_char("BOTTOM_RIGHT")),
398
+ ],
399
+ height=1,
400
+ ),
446
401
  ],
447
402
  style=_style,
448
403
  )
@@ -795,7 +750,7 @@ class Cell:
795
750
  """Set the execution count of the cell."""
796
751
  self.json["execution_count"] = n
797
752
 
798
- def add_output(self, output_json: dict[str, Any]) -> None:
753
+ def add_output(self, output_json: dict[str, Any], own: bool) -> None:
799
754
  """Add a new output to the cell."""
800
755
  # Clear the output if we were previously asked to
801
756
  if self.clear_outputs_on_output:
@@ -26,6 +26,7 @@ from prompt_toolkit.layout.containers import (
26
26
  ConditionalContainer,
27
27
  DynamicContainer,
28
28
  Float,
29
+ to_container,
29
30
  )
30
31
  from prompt_toolkit.layout.controls import FormattedTextControl, UIContent, UIControl
31
32
  from prompt_toolkit.layout.dimension import Dimension
@@ -189,7 +190,7 @@ class Dialog(Float, metaclass=ABCMeta):
189
190
  # Set default body & buttons
190
191
  self.body: AnyContainer = Window()
191
192
  self.buttons: dict[str, Callable | None] = {"OK": None}
192
- self.button_widgets: list[AnyContainer] = []
193
+ self._button_widgets: list[AnyContainer] = []
193
194
 
194
195
  # Create key-bindings
195
196
  kb = KeyBindings()
@@ -225,16 +226,12 @@ class Dialog(Float, metaclass=ABCMeta):
225
226
  )
226
227
 
227
228
  # The buttons.
229
+ self.button_split = VSplit(self.button_widgets, padding=1)
228
230
  buttons_row = ConditionalContainer(
229
231
  Box(
230
- body=DynamicContainer(
231
- lambda: VSplit(
232
- self.button_widgets,
233
- padding=1,
234
- key_bindings=DynamicKeyBindings(lambda: self.buttons_kb),
235
- )
236
- ),
232
+ body=self.button_split,
237
233
  height=Dimension(min=1, max=3, preferred=3),
234
+ key_bindings=DynamicKeyBindings(lambda: self.buttons_kb),
238
235
  ),
239
236
  filter=Condition(lambda: bool(self.buttons)),
240
237
  )
@@ -263,6 +260,16 @@ class Dialog(Float, metaclass=ABCMeta):
263
260
  # Set the body as the float's contents
264
261
  super().__init__(content=self.container)
265
262
 
263
+ @property
264
+ def button_widgets(self) -> list[AnyContainer]:
265
+ """A list of button widgets to show in the dialog's row of buttons."""
266
+ return self._button_widgets
267
+
268
+ @button_widgets.setter
269
+ def button_widgets(self, value: list[AnyContainer]) -> None:
270
+ self._button_widgets = list(value)
271
+ self.button_split.children = [to_container(c) for c in value]
272
+
266
273
  def _button_handler(
267
274
  self, button: str = "", event: KeyPressEvent | None = None
268
275
  ) -> None:
@@ -280,8 +287,7 @@ class Dialog(Float, metaclass=ABCMeta):
280
287
  self.load(**params)
281
288
 
282
289
  # Create button widgets & callbacks
283
- self.button_widgets.clear()
284
-
290
+ new_button_widgets: list[AnyContainer] = []
285
291
  if self.buttons:
286
292
  width = max(map(len, self.buttons)) + 2
287
293
  used_keys = set()
@@ -296,7 +302,7 @@ class Dialog(Float, metaclass=ABCMeta):
296
302
  rest = text
297
303
  # Add a button with a handler
298
304
  handler = partial(self._button_handler, text)
299
- self.button_widgets.append(
305
+ new_button_widgets.append(
300
306
  FocusedStyle(
301
307
  Button(
302
308
  [("underline", key), ("", rest)],
@@ -309,6 +315,7 @@ class Dialog(Float, metaclass=ABCMeta):
309
315
  # Add a key-handler
310
316
  if key:
311
317
  self.buttons_kb.add(f"A-{key.lower()}", is_global=True)(handler)
318
+ self.button_widgets = new_button_widgets
312
319
 
313
320
  # When a button is selected, handle left/right key bindings.
314
321
  if len(self.button_widgets) > 1:
@@ -333,15 +340,13 @@ class Dialog(Float, metaclass=ABCMeta):
333
340
  self._load(**params)
334
341
  self.last_focused = self.app.layout.current_control
335
342
  self._visible = True
336
- if self.to_focus is not None:
337
- self.app.layout.focus(self.to_focus)
338
- else:
339
- try:
340
- self.app.layout.focus(self.container)
341
- except ValueError:
342
- pass
343
- self.app.layout.focus(self.container)
344
- self.app.invalidate()
343
+ to_focus = self.to_focus or self.container
344
+ try:
345
+ self.app.layout.focus(to_focus)
346
+ except ValueError:
347
+ pass
348
+ finally:
349
+ self.app.invalidate()
345
350
 
346
351
  def hide(self, event: KeyPressEvent | None = None) -> None:
347
352
  """Hide the dialog."""
@@ -377,9 +377,9 @@ class FileBrowserControl(UIControl):
377
377
  self.dir = path or UPath(".")
378
378
  self.hovered: int | None = None
379
379
  self.selected: int | None = None
380
- self._dir_cache: FastDictCache[
381
- tuple[Path], list[tuple[bool, Path]]
382
- ] = FastDictCache(get_value=self.load_path, size=1)
380
+ self._dir_cache: FastDictCache[tuple[Path], list[tuple[bool, Path]]] = (
381
+ FastDictCache(get_value=self.load_path, size=1)
382
+ )
383
383
  self.on_select = Event(self, on_select)
384
384
  self.on_chdir = Event(self, on_chdir)
385
385
  self.on_open = Event(self, on_open)
@@ -236,6 +236,7 @@ class Button:
236
236
 
237
237
  def default_mouse_handler(self, mouse_event: MouseEvent) -> NotImplementedOrNone:
238
238
  """Handle mouse events."""
239
+ app = get_app()
239
240
  if self.disabled():
240
241
  return NotImplemented
241
242
  if mouse_event.button == MouseButton.LEFT:
@@ -249,16 +250,15 @@ class Button:
249
250
  ):
250
251
  y_min, x_min = min(render_info._rowcol_to_yx.values())
251
252
  y_max, x_max = max(render_info._rowcol_to_yx.values())
252
- get_app().mouse_limits = WritePosition(
253
+ app.mouse_limits = WritePosition(
253
254
  xpos=x_min,
254
255
  ypos=y_min,
255
- width=x_max - x_min,
256
- height=y_max - y_min,
256
+ width=x_max - x_min + 1,
257
+ height=y_max - y_min + 1,
257
258
  )
258
259
  self.on_mouse_down.fire()
259
260
 
260
261
  if not self.has_focus():
261
- app = get_app()
262
262
  app.layout.focus(self)
263
263
  # Invalidate the app - we don't do it by returning None so the
264
264
  # event can bubble
@@ -269,7 +269,7 @@ class Button:
269
269
  return None
270
270
 
271
271
  elif mouse_event.event_type == MouseEventType.MOUSE_UP:
272
- get_app().mouse_limits = None
272
+ app.mouse_limits = None
273
273
  if self.selected:
274
274
  self.selected = False
275
275
  self.on_click.fire()
@@ -280,11 +280,12 @@ class Button:
280
280
  if (info := self.window.render_info) is not None and (
281
281
  info._x_offset + mouse_event.position.x,
282
282
  info._y_offset + mouse_event.position.y,
283
- ) != get_app().mouse_position:
283
+ ) != app.mouse_position:
284
+ app.mouse_limits = None
284
285
  self.selected = False
285
286
  return None
286
287
 
287
- get_app().mouse_limits = None
288
+ app.mouse_limits = None
288
289
  self.selected = False
289
290
  return NotImplemented
290
291
 
@@ -3,6 +3,7 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import logging
6
+ from functools import lru_cache
6
7
  from typing import TYPE_CHECKING
7
8
 
8
9
  from prompt_toolkit.auto_suggest import AutoSuggest, DynamicAutoSuggest
@@ -97,6 +98,21 @@ if TYPE_CHECKING:
97
98
  log = logging.getLogger(__name__)
98
99
 
99
100
 
101
+ @lru_cache
102
+ def _get_lexer(highlight: bool, lexer: Lexer | None, language: str) -> Lexer:
103
+ """Determine which lexer should be used for syntax highlighting."""
104
+ if not highlight:
105
+ return SimpleLexer()
106
+ elif lexer is not None:
107
+ return lexer
108
+ try:
109
+ pygments_lexer_class = get_lexer_by_name(language).__class__
110
+ except ClassNotFound:
111
+ return SimpleLexer()
112
+ else:
113
+ return PygmentsLexer(pygments_lexer_class, sync_from_start=False)
114
+
115
+
100
116
  class KernelInput(TextArea):
101
117
  """Kernel input text areas.
102
118
 
@@ -182,14 +198,6 @@ class KernelInput(TextArea):
182
198
  self._language = language
183
199
  self.lexer = lexer
184
200
 
185
- def _get_lexer() -> Lexer:
186
- try:
187
- pygments_lexer_class = get_lexer_by_name(self.language).__class__
188
- except ClassNotFound:
189
- return SimpleLexer()
190
- else:
191
- return PygmentsLexer(pygments_lexer_class, sync_from_start=False)
192
-
193
201
  self.formatters = formatters if formatters is not None else []
194
202
  self._diagnostics = diagnostics or Report()
195
203
  self.inspector = inspector
@@ -241,7 +249,11 @@ class KernelInput(TextArea):
241
249
 
242
250
  self.control = BufferControl(
243
251
  buffer=self.buffer,
244
- lexer=DynamicLexer(lambda: self.lexer or _get_lexer()),
252
+ lexer=DynamicLexer(
253
+ lambda: _get_lexer(
254
+ app.config.syntax_highlighting, self.lexer, self.language
255
+ )
256
+ ),
245
257
  input_processors=[
246
258
  ConditionalProcessor(
247
259
  DiagnosticProcessor(_get_diagnostics),
@@ -40,7 +40,7 @@ if TYPE_CHECKING:
40
40
  from prompt_toolkit.filters import FilterOrBool
41
41
  from prompt_toolkit.formatted_text.base import AnyFormattedText, StyleAndTextTuples
42
42
  from prompt_toolkit.key_binding.key_bindings import (
43
- KeyBindings,
43
+ KeyBindingsBase,
44
44
  NotImplementedOrNone,
45
45
  )
46
46
  from prompt_toolkit.layout.containers import AnyContainer, Container, _Split
@@ -84,7 +84,7 @@ class Box:
84
84
  style: str = "",
85
85
  char: None | str | Callable[[], str] = None,
86
86
  modal: bool = False,
87
- key_bindings: KeyBindings | None = None,
87
+ key_bindings: KeyBindingsBase | None = None,
88
88
  ) -> None:
89
89
  """Initialize this widget."""
90
90
  if padding is None:
@@ -117,7 +117,7 @@ class Box:
117
117
  height=height,
118
118
  style=style,
119
119
  modal=modal,
120
- key_bindings=None,
120
+ key_bindings=key_bindings,
121
121
  )
122
122
 
123
123
  def __pt_container__(self) -> Container:
@@ -155,7 +155,7 @@ class ConditionalSplit:
155
155
  return self._cache.get(vertical, partial(self.load_container, vertical))
156
156
 
157
157
  def __pt_container__(self) -> AnyContainer:
158
- """Return a dymanic container."""
158
+ """Return a dynamic container."""
159
159
  return DynamicContainer(self.container)
160
160
 
161
161
 
@@ -287,7 +287,7 @@ class TabBarControl(UIControl):
287
287
 
288
288
  def is_focusable(self) -> bool:
289
289
  """Tell whether this user control is focusable."""
290
- return True
290
+ return False
291
291
 
292
292
  def create_content(self, width: int, height: int) -> UIContent:
293
293
  """Generate the formatted text fragments which make the controls output."""
@@ -77,9 +77,9 @@ class StatusBar:
77
77
  ) -> None:
78
78
  """Create a new status bar instance."""
79
79
  self.default: StatusBarFields = default or ([], [])
80
- self._status_cache: FastDictCache[
81
- tuple[int], list[StyleAndTextTuples]
82
- ] = FastDictCache(self._status, size=1)
80
+ self._status_cache: FastDictCache[tuple[int], list[StyleAndTextTuples]] = (
81
+ FastDictCache(self._status, size=1)
82
+ )
83
83
  self.container = ConditionalContainer(
84
84
  content=VSplit(
85
85
  [
euporie/hub/app.py CHANGED
@@ -89,9 +89,13 @@ class HubApp(BaseApp):
89
89
  )
90
90
 
91
91
  # Import the hubbed app
92
- if entry_point := {
93
- entry.name: entry for entry in entry_points()["euporie.apps"]
94
- }.get(cls.config.app):
92
+ eps = entry_points()
93
+ if isinstance(eps, dict):
94
+ points = eps.get("euporie.apps")
95
+ else:
96
+ points = eps.select(group="euporie.apps")
97
+ apps = {x.name: x for x in points} if points else {}
98
+ if entry_point := apps.get(cls.config.app):
95
99
  app_cls = entry_point.load()
96
100
  else:
97
101
  raise ValueError("Application `%s` not found", cls.config.app)