Skip to content

Commit

Permalink
fix typing errors
Browse files Browse the repository at this point in the history
  • Loading branch information
SkwalExe committed Jun 4, 2024
1 parent 84d6c4b commit 73be6a7
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 74 deletions.
51 changes: 22 additions & 29 deletions src/octologo/__main__.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
# ---------------------------------------- Standard Imports
import os
from collections.abc import Generator
from time import time

# ---------------------------------------- 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, logger, style_names, styles
from octologo.utils import BASE_DIR, get_output_filename, logger, style_names, styles
from octologo.wizard import SelectQuestion, TextQuestion, Wizard, inq_ask

# from textual import log

BASIC_INFO_QUESTIONS = [
TextQuestion(
"name",
Expand All @@ -26,10 +26,6 @@
]


def get_output_filaname(project_name: str) -> str:
return f"octologo_{project_name}_{int(time())}.png"


class OctoLogoApp(App):
BINDINGS = [
("ctrl+q", "quit", "Quit"),
Expand All @@ -40,14 +36,15 @@ class OctoLogoApp(App):
CSS_PATH = os.path.join(BASE_DIR, "app.tcss")
TITLE = "Octo Logo Wizard"
finished: bool = False
save_to: str | None = None
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:
Expand All @@ -68,21 +65,25 @@ def on_wizard_finished(self, message: Wizard.Finished) -> None:
elif finished_wizard_id == "style_wizard":
style = styles[self.answers["style"]].module
self.result = style.get_image(self.answers)
self.save_to = get_output_filaname(self.answers["name"])
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]{self.save_to}[/bold].\n"
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(self.save_to)
self.result.save(save_to)
self.finished = True

def compose(self) -> Generator:
Expand All @@ -98,26 +99,17 @@ def compose(self) -> Generator:
yield self.loading_wid


def disable_ansi(ctx: ExtraContext, param: Parameter, val: bool) -> bool:
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


@extra_command(params=[])
@option(
"-t", "--no-tui", is_flag=True, help="Dont use the Textual Terminal User Interface"
)
@option("-t", "--no-tui", is_flag=True, help="Dont use the Textual Terminal User Interface")
def main(no_tui: bool) -> None:
use_tui = not no_tui

if use_tui:
# If the tui is enabled, run the textual app
app = OctoLogoApp()
app.run()
quit(0)
else:
if no_tui:
# If the tui is disabled, do everything without textual
answers = dict()

Expand All @@ -126,11 +118,12 @@ def main(no_tui: bool) -> None:

style = styles[answers["style"]].module
result = style.get_image(answers)
save_to = get_output_filaname(answers["name"])

save_to = get_output_filename(answers["name"])
result.save(save_to)
logger.success(f"Image saved to : {save_to}")


if __name__ == "__main__":
main()
else:
# If the tui is enabled, run the textual app
app = OctoLogoApp()
app.run()
quit(0)
9 changes: 2 additions & 7 deletions src/octologo/styles/underline_core.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import sys

from octologo.utils import (
FONTS_DIR,
color_scheme_names,
Expand All @@ -15,9 +13,6 @@
from PIL.ImageFont import FreeTypeFont
from textual.validation import Number

sys.path.append("..")


questions = [
SelectQuestion(
"font",
Expand Down Expand Up @@ -52,7 +47,7 @@ def get_image(answers: dict) -> ImageClass:
accent = ImageColor.getrgb(color_schemes[answers["color"]]["accent"])

# Get the width and height of the texts
text_width, text_height = get_text_size(answers["name"], font)
text_width = get_text_size(answers["name"], font)[0]
font_height = get_font_height(font)

# Get the correct image width and height
Expand Down Expand Up @@ -93,7 +88,7 @@ def get_image(answers: dict) -> ImageClass:
underline_start = (underline_start_x, underline_start_y)
underline_end = (underline_end_x, underline_end_y)

underline_pos = [underline_start, underline_end]
underline_pos = (underline_start, underline_end)

# Underline the first letter
draw.rectangle(underline_pos, fill=accent, width=answers["bar_size"])
Expand Down
25 changes: 12 additions & 13 deletions src/octologo/utils.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import os
from importlib import import_module
from sys import stdout
from time import time
from types import ModuleType
from typing import Any

import toml
from loguru import logger
Expand All @@ -11,26 +13,20 @@
logger.remove()
logger.add(
stdout,
format="[ <green>{time:HH:mm:ss}</green> ]"
" - <level>{level}</level> -> "
"<level>{message}</level>",
format="[ <green>{time:HH:mm:ss}</green> ]" " - <level>{level}</level> -> " "<level>{message}</level>",
)


def get_text_size(text: str, font: FreeTypeFont) -> tuple[int, int]:
text_bbox = ImageDraw.Draw(Image.new("RGBA", (1, 1), (0, 0, 0, 0))).textbbox(
(0, 0), text, font=font
)
text_bbox = ImageDraw.Draw(Image.new("RGBA", (1, 1), (0, 0, 0, 0))).textbbox((0, 0), text, font=font)
text_width = text_bbox[2] - text_bbox[0]
text_height = text_bbox[3] - text_bbox[1]

return text_width, text_height


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


def remove_ext(filename: str) -> str:
Expand Down Expand Up @@ -80,9 +76,8 @@ def get_color_schemes() -> dict[str, dict[str, str]]:


color_schemes = get_color_schemes()
color_scheme_names: dict[str, str] = [
(color_schemes[color_scheme]["name"], color_scheme)
for color_scheme in color_schemes
color_scheme_names: list[tuple[str, Any]] = [
(color_schemes[color_scheme]["name"], color_scheme) for color_scheme in color_schemes
]


Expand All @@ -109,6 +104,10 @@ def get_styles() -> dict[str, Style]:
return styles


def get_output_filename(project_name: str) -> str:
return f"octologo_{project_name}_{int(time())}.png"


styles = get_styles()

style_names: dict[str, str] = [(styles[style].display_name, style) for style in styles]
style_names: list[tuple[str, Any]] = [(styles[style].display_name, style) for style in styles]
51 changes: 26 additions & 25 deletions src/octologo/wizard.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,13 @@ class QuestionBase:

def __init__(self, name: str, label: str) -> None:
self.name = name
self.type = type
self.label = label


class TextQuestion(QuestionBase):
validators: list[Validator] | None
placeholder: str
default_value: str
type: str = "text"

def __init__(
self,
Expand All @@ -43,6 +41,7 @@ def __init__(
self.default_value = default_value

def as_widget(self) -> Input:
"""Returns a Textual input widget with the correcponding information"""
_input = Input(
classes="full-width",
id=self.name,
Expand All @@ -55,13 +54,10 @@ def as_widget(self) -> Input:


class SelectQuestion(QuestionBase):
options: list
type: str = "select"
options: list[tuple[str, Any]]
default_value: Any | None = None

def __init__(
self, name: str, label: str, options: list, default_value: str | None = None
) -> None:
def __init__(self, name: str, label: str, options: list[tuple[str, Any]], default_value: str | None = None) -> None:
super().__init__(name, label)
self.options = options
self.default_value = default_value
Expand Down Expand Up @@ -89,17 +85,17 @@ def compose(self) -> Generator:
class Wizard(Static):
question_index = reactive(-1)
answers: dict = dict()
selected_question = None
selected_question: None | Select | Input = None
questions: list[SelectQuestion | TextQuestion]
input_message: Label
title: str = "Wizard"

# The message sent when the "next" button is clicked while on the last question
class Finished(Message):
answers = dict
wizard_id: str
answers: dict
wizard_id: str | None

def __init__(self, answers: dict, wizard_id: str) -> None:
def __init__(self, answers: dict, wizard_id: str | None) -> None:
self.answers = answers
self.wizard_id = wizard_id
super().__init__()
Expand All @@ -120,7 +116,10 @@ async def on_next(self) -> None:
else:
self.question_index += 1

def handle_validation_result(self, validation_result: ValidationResult) -> None:
def handle_validation_result(self, validation_result: ValidationResult | None) -> None:
if self.selected_question is None:
raise Exception("selected_question should not be None")

if validation_result is None or validation_result.is_valid:
# If the validation is OK then hide the error message and set the input color back to normal
# Also, reenable the next button
Expand Down Expand Up @@ -149,7 +148,7 @@ def on_input_submitted(self, message: Input.Submitted) -> None:
self.handle_validation_result(message.validation_result)

# When the input is submitted, if it is valid then go to the next question
if message.validation_result.is_valid:
if message.validation_result is None or message.validation_result.is_valid:
self.question_index += 1

def on_select_changed(self, message: Select.Changed) -> None:
Expand Down Expand Up @@ -178,9 +177,7 @@ def compose(self) -> Generator:
yield wid

# The error message below inputs if there are any errors
self.input_message = Label(
"This is the input error message", id="input_message", classes="hidden"
)
self.input_message = Label("This is the input error message", id="input_message", classes="hidden")
self.input_message.styles.color = "tomato"
self.input_message.styles.max_width = "100%"
yield self.input_message
Expand All @@ -193,25 +190,26 @@ def on_mount(self) -> None:
self.question_index = 0

def watch_question_index(self) -> None:
# Remove the selected class from the previous shown input if any
"""Called when the question index changes"""

# Add 'hidden' class to the previous shown input if any
if self.selected_question is not None:
self.selected_question.add_class("hidden")

# If the question index has been incremented but it is now out of bound then
# the user clicked next on the last question
if self.question_index == len(self.questions):
if self.question_index >= len(self.questions):
self.post_message(self.Finished(self.answers, self.id))
return

# Put the question index in the border title
self.border_title = (
f"{self.title} [{self.question_index + 1}/{len(self.questions)}]"
)
self.border_title = f"{self.title} [{self.question_index + 1}/{len(self.questions)}]"

# Show the input corresponding to the new value of self.question_index
self.selected_question = self.query_one(
f"#{self.questions[self.question_index].name}"
)
element = self.query_one(f"#{self.questions[self.question_index].name}")
if not isinstance(element, (Input, Select)):
raise Exception("Wanted Input or Select but got something else")
self.selected_question = element
self.selected_question.remove_class("hidden")
self.selected_question.focus()

Expand All @@ -220,7 +218,10 @@ def watch_question_index(self) -> None:
self.query_one("#back").disabled = self.question_index == 0


def validate_text_question(question: SelectQuestion | TextQuestion, value: str) -> bool:
def validate_text_question(question: TextQuestion, value: str) -> bool:
if question.validators is None:
return True

for validator in question.validators:
validation = validator.validate(value)
if not validation.is_valid:
Expand Down

0 comments on commit 73be6a7

Please sign in to comment.