Skip to content

Commit

Permalink
🔨 Admonition dialog class for displaying scrolling text
Browse files Browse the repository at this point in the history
Signed-off-by: rjdbcm <[email protected]>
  • Loading branch information
rjdbcm committed Jun 24, 2024
1 parent 5760da1 commit 02dff10
Showing 1 changed file with 166 additions and 60 deletions.
226 changes: 166 additions & 60 deletions ozi/new/interactive.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,37 @@
import sys
from functools import lru_cache
from typing import TYPE_CHECKING
from typing import Any
from typing import Sequence
from typing import TypeVar

import requests
from prompt_toolkit import Application
from prompt_toolkit.application.current import get_app
from prompt_toolkit.document import Document
from prompt_toolkit.key_binding import KeyBindings
from prompt_toolkit.key_binding import merge_key_bindings
from prompt_toolkit.key_binding.bindings.focus import focus_next
from prompt_toolkit.key_binding.bindings.focus import focus_previous
from prompt_toolkit.key_binding.defaults import load_key_bindings
from prompt_toolkit.layout import HSplit
from prompt_toolkit.layout import Layout
from prompt_toolkit.shortcuts import button_dialog
from prompt_toolkit.shortcuts import checkboxlist_dialog
from prompt_toolkit.shortcuts import input_dialog
from prompt_toolkit.shortcuts import message_dialog
from prompt_toolkit.shortcuts import radiolist_dialog
from prompt_toolkit.shortcuts import yes_no_dialog
from prompt_toolkit.styles import BaseStyle
from prompt_toolkit.styles import Style
from prompt_toolkit.validation import DynamicValidator
from prompt_toolkit.validation import ThreadedValidator
from prompt_toolkit.validation import ValidationError
from prompt_toolkit.validation import Validator
from prompt_toolkit.widgets import Button
from prompt_toolkit.widgets import Dialog
from prompt_toolkit.widgets import Label
from prompt_toolkit.widgets import RadioList

from ozi.spec import METADATA
from ozi.trove import Prefix
Expand Down Expand Up @@ -82,24 +98,109 @@ def validate(self, document: Document) -> None: # pragma: no cover # noqa: ANN
raise ValidationError(len(document.text), 'package not found')


style = Style.from_dict(
{
'dialog': 'bg:#030711 fg:#030711',
'dialog.body checkbox-list': '#e1e7ef',
'dialog.body checkbox': '#e1e7ef',
'dialog.body checkbox-selected': 'bg:#192334',
'dialog.body checkbox-checked': '#e1e7ef',
'dialog.body radio-list': '#e1e7ef',
'dialog.body radio': '#e1e7ef',
'dialog.body radio-selected': 'bg:#192334',
'dialog.body radio-checked': '#e1e7ef',
'button': '#e1e7ef',
'dialog label': '#e1e7ef',
'frame.border': '#192334',
'dialog.body': 'bg:#030711',
'dialog shadow': 'bg:#192334',
},
)
_style_dict = {
'dialog': 'bg:#030711 fg:#030711',
'dialog.body checkbox-list': '#e1e7ef',
'dialog.body checkbox': '#e1e7ef',
'dialog.body checkbox-selected': 'bg:#192334',
'dialog.body checkbox-checked': '#e1e7ef',
'dialog.body radio-list': '#e1e7ef',
'dialog.body radio': '#e1e7ef',
'dialog.body radio-selected': 'bg:#192334',
'dialog.body radio-checked': '#e1e7ef',
'button': '#e1e7ef',
'dialog label': '#e1e7ef',
'frame.border': '#192334',
'dialog.body': 'bg:#030711',
'dialog shadow': 'bg:#192334',
}

_style = Style.from_dict(_style_dict)

_T = TypeVar('_T')


class Admonition(RadioList):
open_character = ''
close_character = ''
container_style = 'class:admonition-list'
default_style = 'class:admonition'
selected_style = 'class:admonition-selected'
checked_style = 'class:admonition-checked'
multiple_selection = False

def __init__(
self,
values: Sequence[tuple[_T, Any]],
default: _T | None = None,
) -> None:
super().__init__(values, default)

def _handle_enter(self) -> None:
pass


def admonition_dialog(
title: str = '',
text: str = '',
ok_text: str = '✔ Ok',
cancel_text: str = '✘ Exit',
style: BaseStyle | None = None,
) -> Application[list[_T]]:
"""
Display a simple list of element the user can choose multiple values amongst.
Several elements can be selected at a time using Arrow keys and Enter.
The focus can be moved between the list and the Ok/Cancel button with tab.
"""

def _return_none() -> None:
"""Button handler that returns None."""
get_app().exit()

if style is None:
style_dict = _style_dict
style_dict.update(
{
'dialog.body admonition-list': '#e1e7ef',
'dialog.body admonition': '#030711',
'dialog.body admonition-selected': 'bg:#030711 fg:#030711',
'dialog.body admonition-checked': '#030711',
},
)
style = Style.from_dict(style_dict)

def ok_handler() -> None:
get_app().exit(result=True)

lines = text.splitlines()

cb_list = Admonition(values=list(zip(lines, lines)), default=None)

dialog = Dialog(
title=title,
body=HSplit(
[Label(text='Disclaimer', dont_extend_height=True), cb_list],
padding=1,
),
buttons=[
Button(text=ok_text, handler=ok_handler),
Button(text=cancel_text, handler=_return_none),
],
with_background=True,
width=len(max(lines, key=len)) + 8,
)
bindings = KeyBindings()
bindings.add('tab')(focus_next)
bindings.add('s-tab')(focus_previous)

return Application(
layout=Layout(dialog),
key_bindings=merge_key_bindings([load_key_bindings(), bindings]),
mouse_support=True,
style=style,
full_screen=True,
)


def validate_message(
Expand Down Expand Up @@ -134,7 +235,7 @@ def classifier_checkboxlist(key: str) -> list[str] | None: # pragma: no cover
),
title='ozi-new interactive prompt',
text=f'Please select {key} classifier or classifiers:',
style=style,
style=_style,
ok_text='✔ Ok',
cancel_text='← Back',
).run()
Expand All @@ -152,7 +253,7 @@ def header_input(
title='ozi-new interactive prompt',
text='\n'.join(('\n'.join(prefix.values()), '\n', *args)),
validator=validator,
style=style,
style=_style,
cancel_text='☰ Menu',
ok_text='✔ Ok',
).run()
Expand All @@ -173,7 +274,7 @@ def header_input(
message_dialog(
title='ozi-new interactive prompt',
text=f'Invalid input "{header}"\n{errmsg}\nPress ENTER to continue.',
style=style,
style=_style,
ok_text='✔ Ok',
).run()
return None, output, prefix
Expand All @@ -195,22 +296,22 @@ def menu_loop(
('✘ Exit', None),
('← Back', True),
],
style=style,
style=_style,
).run():
case True:
break
case False:
if yes_no_dialog(
title='ozi-new interactive prompt',
text='Reset prompt and start over?',
style=style,
style=_style,
).run():
return ['interactive', '.'], output, prefix
case None:
if yes_no_dialog(
title='ozi-new interactive prompt',
text='Exit the prompt?',
style=style,
style=_style,
).run():
return [], output, prefix
case 0:
Expand All @@ -232,7 +333,7 @@ def menu_loop(
('README', 0),
('← Back', True),
],
style=style,
style=_style,
).run():
case True:
break
Expand All @@ -251,7 +352,7 @@ def menu_loop(
'Please select README type:',
),
),
style=style,
style=_style,
ok_text='✔ Ok',
cancel_text='← Back',
).run()
Expand Down Expand Up @@ -295,30 +396,35 @@ def check_package_exists() -> Validator:
else:
return ProjectNameValidator()

message_dialog(
title='ozi-new interactive prompt',
text="""
The information provided on this prompt does not, and is not intended to,
constitute legal advice. All information, content, and materials available
on this prompt are for general informational purposes only. Information on this
prompt may not constitute the most up-to-date legal or other information.
THE LICENSE TEMPLATES, LICENSE IDENTIFIERS, LICENSE CLASSIFIERS, AND LICENSE
EXPRESSION PARSING SERVICES, AND ALL OTHER CONTENTS ARE PROVIDED "AS IS",
NO REPRESENTATIONS ARE MADE THAT THE CONTENT IS ERROR-FREE AND/OR APPLICABLE
FOR ANY PURPOSE, INCLUDING MERCHANTABILITY.
Readers of this prompt should contact their attorney to obtain advice with
respect to any particular legal matter. The OZI Project is not a law firm
and does not provide legal advice. No reader or user of this prompt should
act or abstain from acting on the basis of information on this prompt without
first seeking legal advice from counsel in the relevant jurisdiction.
Legal counsel can ensure that the information provided in this prompt is
applicable to your particular situation. Use of, or reading, this prompt or any
of resources contained within does not create an attorney-client relationship.
if (
admonition_dialog(
title='ozi-new interactive prompt',
text="""
The information provided on this prompt does not, and is not intended
to, constitute legal advice. All information, content, and materials
available on this prompt are for general informational purposes only.
Information on this prompt may not constitute the most up-to-date
legal or other information.
THE LICENSE TEMPLATES, LICENSE IDENTIFIERS, LICENSE CLASSIFIERS,
AND LICENSE EXPRESSION PARSING SERVICES, AND ALL OTHER CONTENTS ARE
PROVIDED "AS IS", NO REPRESENTATIONS ARE MADE THAT THE CONTENT IS
ERROR-FREE AND/OR APPLICABLE FOR ANY PURPOSE, INCLUDING MERCHANTABILITY.
Readers of this prompt should contact their attorney to obtain advice
with respect to any particular legal matter. The OZI Project is not a
law firm and does not provide legal advice. No reader or user of this
prompt should act or abstain from acting on the basis of information
on this prompt without first seeking legal advice from counsel in the
relevant jurisdiction. Legal counsel can ensure that the information
provided in this prompt is applicable to your particular situation.
Use of, or reading, this prompt or any of the resources contained
within does not create an attorney-client relationship.
""",
style=style,
).run()
).run()
is None
):
return []

prefix: dict[str, str] = {}
output = ['project']
Expand Down Expand Up @@ -413,7 +519,7 @@ def check_package_exists() -> Validator:
text='\n'.join(
('\n'.join(prefix.values()), '\n', 'Please select a license classifier:'),
),
style=style,
style=_style,
cancel_text='☰ Menu',
ok_text='✔ Ok',
).run()
Expand All @@ -425,7 +531,7 @@ def check_package_exists() -> Validator:
if validate_message(license_ if license_ else '', LengthValidator())[0]:
break
message_dialog(
style=style,
style=_style,
title='ozi-new interactive prompt',
text=f'Invalid input "{license_}"\nPress ENTER to continue.',
ok_text='✔ Ok',
Expand All @@ -447,7 +553,7 @@ def check_package_exists() -> Validator:
('\n'.join(prefix.values()), '\n', 'Edit SPDX license expression:'),
),
default='',
style=style,
style=_style,
cancel_text='Skip',
).run()
elif len(possible_spdx) == 1:
Expand All @@ -457,7 +563,7 @@ def check_package_exists() -> Validator:
('\n'.join(prefix.values()), '\n', 'Edit SPDX license expression:'),
),
default=possible_spdx[0],
style=style,
style=_style,
cancel_text='Skip',
ok_text='✔ Ok',
).run()
Expand All @@ -468,7 +574,7 @@ def check_package_exists() -> Validator:
text='\n'.join(
('\n'.join(prefix.values()), '\n', 'Please select a SPDX license-id:'),
),
style=style,
style=_style,
cancel_text='☰ Menu',
ok_text='✔ Ok',
).run()
Expand All @@ -483,15 +589,15 @@ def check_package_exists() -> Validator:
('\n'.join(prefix.values()), '\n', 'Edit SPDX license expression:'),
),
default=license_id if license_id is not None else '',
style=style,
style=_style,
cancel_text='Skip',
ok_text='✔ Ok',
).run()
if validate_message(license_id if license_id else '', LengthValidator())[0]:
break
else:
message_dialog(
style=style,
style=_style,
title='ozi-new interactive prompt',
text=f'Invalid input "{license_id}"\nPress ENTER to continue.',
ok_text='✔ Ok',
Expand All @@ -517,7 +623,7 @@ def check_package_exists() -> Validator:
'Are there any maintainers of this project?\n(other than the author or authors)',
),
),
style=style,
style=_style,
).run():
while True:
result, output, prefix = header_input(
Expand Down Expand Up @@ -560,13 +666,13 @@ def check_package_exists() -> Validator:
('Yes', True),
('No', False),
],
style=style,
style=_style,
).run():
requirement = input_dialog(
title='ozi-new interactive prompt',
text='\n'.join(('\n'.join(prefix.values()), '\n', 'Search PyPI packages:')),
validator=PackageValidator(),
style=style,
style=_style,
cancel_text='← Back',
).run()
requires_dist += [requirement] if requirement else []
Expand All @@ -590,7 +696,7 @@ def check_package_exists() -> Validator:
),
yes_text='✔ Ok',
no_text='☰ Menu',
style=style,
style=_style,
).run():
result, output, prefix = menu_loop(output, prefix)
if result is not None:
Expand Down

0 comments on commit 02dff10

Please sign in to comment.