Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Textual wizard #43

Merged
merged 2 commits into from
Jul 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions .github/workflows/pr_checks.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Check for typing/linting/formatting/test errors
name: Check for typing/linting/formatting errors

on:
pull_request:
Expand All @@ -25,6 +25,4 @@ jobs:
run: pdm run lint-check
- name: Check for type errors
run: pdm run check-types
- name: Run tests
run: pdm run tests

7 changes: 2 additions & 5 deletions .github/workflows/pypi_publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,8 @@ name: Publish to PyPI

on:
workflow_dispatch:
push:
branches:
- 'main'
paths:
- 'pyproject.toml'
release:
types: [published]

jobs:
publish:
Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
# v3.1.0 - 2024-07-22

### Changed
- Now using the textual-wizard library instead of the prototype that was precedently used in this project.

# v3.0.2 - 2024-06-14

### Fixed
Expand Down
1,241 changes: 631 additions & 610 deletions pdm.lock

Large diffs are not rendered by default.

25 changes: 13 additions & 12 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,34 +17,35 @@ dependencies = [
"textual>=0.38.1",
"click-extra>=4.7.2",
"loguru>=0.7.2",
"textual-wizard>=0.4.3",
]
dynamic = ["version"]

[tool.pdm]
distribution = true
[tool.pdm.version]
source = "file"
path = "src/octologo/__init__.py"

[project.urls]
Home = "https://github.com/SkwalExe/octo-logo"

[project.scripts]
octologo = "octologo.__main__:main"

[tool.pdm]
distribution = true

[tool.pdm.dev-dependencies]
dev = [
"ruff>=0.4.7",
"pytest>=8.2.2",
"pytest-asyncio>=0.23.7",
"pyright>=1.1.365",
"textual-dev>=1.5.1",
]

[tool.pdm.scripts]
format = "ruff format"
dev = "textual run --dev -c octologo"
format-check = "ruff format --check"
lint = "ruff check --fix --show-fixes"
lint-check = "ruff check"
check-types = "pyright"
tests = "pytest"
console = "textual console -x EVENT -x SYSTEM"

[project.urls]
Home = "https://github.com/SkwalExe/octo-logo"

[project.scripts]
octologo = "octologo.__main__:main"

140 changes: 29 additions & 111 deletions src/octologo/__main__.py
Original file line number Diff line number Diff line change
@@ -1,129 +1,47 @@
# ---------------------------------------- Standard Imports
import os
from collections.abc import Generator
from click_extra import extra_command, option
from textual_wizard import Wizard
from textual_wizard.inputs import Select, Text

# ---------------------------------------- External Imports
from click_extra import ExtraContext, Parameter, extra_command, option
from PIL.Image import Image
from textual.app import App
from textual.events import Key
from textual.validation import Length
from textual.widgets import Footer, Header, Label, LoadingIndicator

# ---------------------------------------- Local Imports
from octologo import __version__
from octologo.utils import BASE_DIR, get_output_filename, logger, style_names, styles
from octologo.wizard import SelectQuestion, TextQuestion, Wizard, inq_ask
from octologo.utils import get_output_filename, logger, style_names, styles

BASIC_INFO_QUESTIONS = [
TextQuestion(
Text(
"name",
"Your project's name",
[Length(1, failure_description="Your project's name cannot be blank")],
"super-octo-project",
placeholder="super-octo-project",
),
SelectQuestion("style", "Logo Style", style_names, "first_letter_underlined"),
Select("style", "Logo Style", options=style_names, default_value="first_letter_underlined"),
]


class OctoLogoApp(App):
BINDINGS = [
("ctrl+q", "quit", "Quit"),
("ctrl+t", "toggle_dark", "Toggle Dark Mode"),
]
answers = dict()

CSS_PATH = os.path.join(BASE_DIR, "app.tcss")
TITLE = "Octo Logo Wizard"
finished: bool = False
result: Image | None = None
loading_wid: LoadingIndicator = LoadingIndicator(classes="hidden")

async def on_key(self, event: Key) -> None:
if event.key == "enter" and self.finished:
await self.action_quit()
elif event.key == "v" and self.finished:
if self.result is None:
raise Exception("self.result should not be null")
self.result.show()

def on_wizard_finished(self, message: Wizard.Finished) -> None:
# Get the wizard answers and the wizard's id
self.answers.update(message.answers)
finished_wizard_id = message.wizard_id

# remove the wizard
self.query_one(f"#{finished_wizard_id}").remove()

# When the basic info wizard is finished, mount the style-specific wizard
if finished_wizard_id == "basic_info_wizard":
style_wizard = Wizard(id="style_wizard")
style_wizard.questions = styles[self.answers["style"]].module.questions
style_wizard.title = "Style Settings"
self.mount(style_wizard)
# When the style-specific wizard is finished, create the image and save it
elif finished_wizard_id == "style_wizard":
style = styles[self.answers["style"]].module
self.result = style.get_image(self.answers)
self.loading_wid.remove_class("hidden")
self.set_timer(2, self.final_message)

# Final message
def final_message(self) -> None:
if self.result is None:
raise Exception("self.result should not be none in OctoLogoApp.final_message")

save_to = get_output_filename(self.answers["name"])

self.loading_wid.add_class("hidden")
self.mount(
Label(
f"Logo saved to [bold]{save_to}[/bold].\n"
f"[blue blink]-> Press v to view the result[/blue blink]\n"
f"[red]Press enter to quit[/red]"
)
)
self.result.save(save_to)
self.finished = True

def compose(self) -> Generator:
self.app.title = f"Octo Logo v{__version__}"

yield Header(show_clock=True)
yield Footer()

basic_info_wizard = Wizard(id="basic_info_wizard")
basic_info_wizard.questions = BASIC_INFO_QUESTIONS
basic_info_wizard.title = "Basic Information"
yield basic_info_wizard
yield self.loading_wid


def disable_ansi(ctx: ExtraContext, _: Parameter, val: bool) -> bool:
ctx.color = not val

# We must return the value for the main function no_ansi parameter not to be None
return val
def handle_wizard_cancelled() -> None:
logger.error("Wizard cancelled by user.")
quit(0)


@extra_command(params=[])
@option("-t", "--no-tui", is_flag=True, help="Dont use the Textual Terminal User Interface")
def main(no_tui: bool) -> None:
if no_tui:
# If the tui is disabled, do everything without textual
answers = dict()
app_title = f"Octologo v{__version__}"
wiz1 = Wizard(BASIC_INFO_QUESTIONS, app_title, "Basic Information", disable_tui=no_tui)
answers = wiz1.run()

if answers is None:
handle_wizard_cancelled()
return

style = styles[answers["style"]].module
wiz2 = Wizard(style.questions, app_title, "Specific Information", disable_tui=no_tui)
res2 = wiz2.run()
if res2 is None:
handle_wizard_cancelled()
return

answers.update(inq_ask(BASIC_INFO_QUESTIONS))
answers.update(inq_ask(styles[answers["style"]].module.questions))
answers.update(res2)

style = styles[answers["style"]].module
result = style.get_image(answers)
result_image = style.get_image(answers)

save_to = get_output_filename(answers["name"])
result.save(save_to)
logger.success(f"Image saved to : {save_to}")
else:
# If the tui is enabled, run the textual app
app = OctoLogoApp()
app.run()
quit(0)
save_to = get_output_filename(answers["name"])
result_image.save(save_to)
logger.success(f"Image saved to : {save_to}")
28 changes: 17 additions & 11 deletions src/octologo/styles/underline_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,32 @@
get_text_size,
os,
)
from octologo.wizard import SelectQuestion, TextQuestion
from PIL import Image, ImageColor, ImageDraw, ImageFont
from PIL.Image import Image as ImageClass
from PIL.ImageFont import FreeTypeFont
from textual.validation import Number
from textual_wizard.inputs import Select, Text

questions = [
SelectQuestion(
Select(
"font",
"Select a font",
[(font, font) for font in font_list],
"Iosevka-Nerd-Font-Complete.ttf",
options=[(font, font) for font in font_list],
default_value="Iosevka-Nerd-Font-Complete.ttf",
),
Select("color", "Select a color scheme", options=color_scheme_names, default_value="adi1090x"),
Text("underline_count", "Lettrs to undrline", validators=[Number(minimum=0)], initial_value="1", placeholder="1"),
Text("padding_x", "Padding x (px)", validators=[Number()], initial_value="200", placeholder="200"),
Text("padding_y", "Padding y (px)", validators=[Number()], initial_value="20", placeholder="20"),
Text("gap", "Gap between text and bar (px)", validators=[Number()], initial_value="20", placeholder="20"),
Text("bar_size", "Bar weight (px)", validators=[Number()], initial_value="20", placeholder="20"),
Text(
"additionnal_bar_width",
"Additionnal bar width (px)",
validators=[Number()],
initial_value="20",
placeholder="20",
),
SelectQuestion("color", "Select a color scheme", color_scheme_names, "adi1090x"),
TextQuestion("underline_count", "Lettrs to undrline", [Number(minimum=0)], "1", "1"),
TextQuestion("padding_x", "Padding x (px)", [Number()], "200", "200"),
TextQuestion("padding_y", "Padding y (px)", [Number()], "20", "20"),
TextQuestion("gap", "Gap between text and bar (px)", [Number()], "20", "20"),
TextQuestion("bar_size", "Bar weight (px)", [Number()], "20", "20"),
TextQuestion("additionnal_bar_width", "Additionnal bar width (px)", [Number()], "20", "20"),
]

active = False
Expand Down
2 changes: 1 addition & 1 deletion src/octologo/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def get_text_size(text: str, font: FreeTypeFont) -> tuple[int, int]:


def get_font_height(font: FreeTypeFont) -> int:
return font.getbbox("azertyuiopqsdfghjklmwxcvbnAZERTYUIOPQASDFGHJKLMWXCVBN0123456789")[3]
return int(font.getbbox("azertyuiopqsdfghjklmwxcvbnAZERTYUIOPQASDFGHJKLMWXCVBN0123456789")[3])


def remove_ext(filename: str) -> str:
Expand Down
Loading