Skip to content

Commit

Permalink
Forbid instantiation of TopLevel/Checker dialogs
Browse files Browse the repository at this point in the history
In preparation for more checker-specific behavior

This makes each checker dialog store its own geometry.
  • Loading branch information
windymilla committed Dec 31, 2024
1 parent 0a01b9c commit 4a5553c
Show file tree
Hide file tree
Showing 10 changed files with 93 additions and 31 deletions.
6 changes: 6 additions & 0 deletions src/guiguts/checkers.py
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,12 @@ def copy_errors() -> None:
self.selected_text_range: Optional[IndexRange] = None
self.reset()

def __new__(cls, *args: Any, **kwargs: Any) -> "CheckerDialog":
"""Ensure CheckerDialogs are not instantiated directly."""
if cls is CheckerDialog:
raise TypeError(f"only children of '{cls.__name__}' may be instantiated")
return object.__new__(cls)

@classmethod
def show_dialog(
cls: type[TlDlg],
Expand Down
10 changes: 7 additions & 3 deletions src/guiguts/footnotes.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,14 +99,18 @@ def __init__(
self.an_index = an_index


class FootnoteCheckerDialog(CheckerDialog):
"""Minimal class to identify dialog type."""


class FootnoteChecker:
"""Find, check & record footnotes."""

def __init__(self, checker_dialog: CheckerDialog) -> None:
def __init__(self, checker_dialog: FootnoteCheckerDialog) -> None:
"""Initialize footnote checker."""
self.fn_records: list[FootnoteRecord] = []
self.an_records: list[AnchorRecord] = []
self.checker_dialog: CheckerDialog = checker_dialog
self.checker_dialog: FootnoteCheckerDialog = checker_dialog
self.fn_index_style = PersistentString(PrefKey.FOOTNOTE_INDEX_STYLE)
self.fn_check_errors = False
self.fns_have_been_moved = False
Expand Down Expand Up @@ -1270,7 +1274,7 @@ def footnote_check() -> None:
if not tool_save():
return

checker_dialog = CheckerDialog.show_dialog(
checker_dialog = FootnoteCheckerDialog.show_dialog(
"Footnote Check Results",
rerun_command=footnote_check,
sort_key_alpha=sort_key_type,
Expand Down
20 changes: 17 additions & 3 deletions src/guiguts/illo_sn_fixup.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,25 @@ def __init__(self, first: str, last: str) -> None:
self.last_line_num = last


class IlloSNCheckerDialog(CheckerDialog):
"""Minimal class to identify dialog type."""


class IlloCheckerDialog(IlloSNCheckerDialog):
"""Minimal class to identify dialog type."""


class SNCheckerDialog(IlloSNCheckerDialog):
"""Minimal class to identify dialog type."""


class IlloSNChecker:
"""Find, check & record Illos or SNs."""

def __init__(self, checker_dialog: CheckerDialog) -> None:
def __init__(self, checker_dialog: IlloSNCheckerDialog) -> None:
"""Initialize IlloSNChecker."""
self.illosn_records: list[IlloSNRecord] = []
self.checker_dialog: CheckerDialog = checker_dialog
self.checker_dialog: IlloSNCheckerDialog = checker_dialog

def reset(self) -> None:
"""Reset IlloSNChecker."""
Expand Down Expand Up @@ -579,7 +591,9 @@ def illosn_check(tag_type: str) -> None:

if not tool_save():
return
checker_dialog = CheckerDialog.show_dialog(
assert tag_type in ("Illustration", "Sidenote")
dialog_type = IlloCheckerDialog if tag_type == "Illustration" else SNCheckerDialog
checker_dialog = dialog_type.show_dialog(
f"{tag_type} Check Results",
rerun_command=lambda: illosn_check(tag_type),
show_suspects_only=True,
Expand Down
36 changes: 25 additions & 11 deletions src/guiguts/misc_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,18 @@
DEFAULT_STEALTH_SCANNOS = "en-common.json"


class BasicFixupCheckerDialog(CheckerDialog):
"""Minimal class to identify dialog type."""


class UnmatchedCheckerDialog(CheckerDialog):
"""Minimal class to identify dialog type."""


class AsteriskCheckerDialog(CheckerDialog):
"""Minimal class to identify dialog type."""


def tool_save() -> bool:
"""File must be saved before running tool, so check if it has been,
and if not, check if user wants to save, or cancel the tool run.
Expand All @@ -67,8 +79,10 @@ def process_fixup(checker_entry: CheckerEntry) -> None:
if checker_entry.text_range is None:
return
entry_type = checker_entry.text.split(":", 1)[0]
start_mark = CheckerDialog.mark_from_rowcol(checker_entry.text_range.start)
end_mark = CheckerDialog.mark_from_rowcol(checker_entry.text_range.end)
start_mark = BasicFixupCheckerDialog.mark_from_rowcol(
checker_entry.text_range.start
)
end_mark = BasicFixupCheckerDialog.mark_from_rowcol(checker_entry.text_range.end)
match_text = maintext().get(start_mark, end_mark)
# Several fixup errors are "Spaced ..." and need spaces removing.
if entry_type.startswith("Spaced "):
Expand Down Expand Up @@ -117,7 +131,7 @@ def basic_fixup_check() -> None:
if not tool_save():
return

checker_dialog = CheckerDialog.show_dialog(
checker_dialog = BasicFixupCheckerDialog.show_dialog(
"Basic Fixup Check Results",
rerun_command=basic_fixup_check,
process_command=process_fixup,
Expand Down Expand Up @@ -689,11 +703,11 @@ def toggle_quote(quote_in: str) -> tuple[str, bool]:
return "[‘’]", True
assert False, f"'{quote_in}' is not a curly quote"

def unmatched_single_quotes(dialog: CheckerDialog) -> None:
def unmatched_single_quotes(dialog: UnmatchedCheckerDialog) -> None:
"""Add warnings about unmatched single quotes to given dialog.
Args:
dialog: Checkerdialog to receive error messages.
dialog: UnmatchedCheckerDialog to receive error messages.
"""
nestable = preferences.get(PrefKey.UNMATCHED_NESTABLE)
prefix = "Unmatched: "
Expand Down Expand Up @@ -807,11 +821,11 @@ def match_pair_block_markup(markup_in: str) -> tuple[str, bool]:
block_type = re.escape(block_type)
return rf"^(/{block_type}(\[\d+)?(\.\d+)?(,\d+)?]?|{block_type}/)$", close

def malformed_block_markup(dialog: CheckerDialog) -> None:
def malformed_block_markup(dialog: UnmatchedCheckerDialog) -> None:
"""Add warnings about malformed block markup to given dialog.
Args:
dialog: Checkerdialog to receive error messages.
dialog: UnmatchedCheckerDialog to receive error messages.
"""
search_range = IndexRange(maintext().start(), maintext().end())
prefix = "Badly formed markup: "
Expand Down Expand Up @@ -885,7 +899,7 @@ def unmatched_markup_check(
nest_reg: Optional[str] = None,
ignore_reg: Optional[str] = None,
sort_key_alpha: Optional[Callable[[CheckerEntry], tuple]] = None,
additional_check_command: Optional[Callable[[CheckerDialog], None]] = None,
additional_check_command: Optional[Callable[[UnmatchedCheckerDialog], None]] = None,
) -> None:
"""Check the currently loaded file for unmatched markup errors.
Expand All @@ -903,7 +917,7 @@ def unmatched_markup_check(
if not tool_save():
return

checker_dialog = CheckerDialog.show_dialog(
checker_dialog = UnmatchedCheckerDialog.show_dialog(
title,
rerun_command=rerun_command,
sort_key_alpha=sort_key_alpha,
Expand Down Expand Up @@ -1170,7 +1184,7 @@ def proofer_comment_check() -> None:
)

class ProoferCommentCheckerDialog(CheckerDialog):
"""Minimal class inheriting from CheckerDialog so that it can exist
"""Minimal class to identify dialog type so that it can exist
simultaneously with other checker dialogs."""

checker_dialog = ProoferCommentCheckerDialog.show_dialog(
Expand Down Expand Up @@ -1212,7 +1226,7 @@ def asterisk_check() -> None:
regexp=True,
)

checker_dialog = CheckerDialog.show_dialog(
checker_dialog = AsteriskCheckerDialog.show_dialog(
"Asterisk Check", rerun_command=asterisk_check
)
ToolTip(
Expand Down
3 changes: 1 addition & 2 deletions src/guiguts/search.py
Original file line number Diff line number Diff line change
Expand Up @@ -470,8 +470,7 @@ def findall_clicked(self) -> None:
return

class FindAllCheckerDialog(CheckerDialog):
"""Minimal class inheriting from CheckerDialog so that it can exist
simultaneously with other checker dialogs."""
"""Minimal class to identify dialog typepylint."""

checker_dialog = FindAllCheckerDialog.show_dialog(
"Search Results", rerun_command=self.findall_clicked
Expand Down
6 changes: 5 additions & 1 deletion src/guiguts/spell.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ def __init__(self, index: IndexRowCol, word: str, bad_word: bool):
self.bad_word = bad_word


class SpellCheckerDialog(CheckerDialog):
"""Minimal class to identify dialog type."""


class SpellChecker:
"""Provides spell check functionality."""

Expand Down Expand Up @@ -358,7 +362,7 @@ def process_spelling(checker_entry: CheckerEntry) -> None:
if checker_entry.text_range:
add_project_word_callback(checker_entry.text.split(maxsplit=1)[0])

checker_dialog = CheckerDialog.show_dialog(
checker_dialog = SpellCheckerDialog.show_dialog(
"Spelling Check Results",
rerun_command=lambda: spell_check(
project_dict, add_project_word_callback, add_global_word_callback
Expand Down
20 changes: 13 additions & 7 deletions src/guiguts/tools/jeebies.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ def __init__(self, file: str) -> None:
self.file = file


class JeebiesCheckerDialog(CheckerDialog):
"""Minimal class to identify dialog type."""


class JeebiesChecker:
"""Provides jeebies check functionality."""

Expand All @@ -65,7 +69,7 @@ def check_for_jeebies_in_file(self) -> None:
"""Check for jeebies in the currently loaded file."""

# Create the checker dialog to show results
checker_dialog = CheckerDialog.show_dialog(
checker_dialog = JeebiesCheckerDialog.show_dialog(
"Jeebies Results",
rerun_command=jeebies_check,
process_command=self.process_jeebies,
Expand Down Expand Up @@ -286,7 +290,7 @@ def find_and_report_2_word_hebe_suspects(
paragraph_start_line_numbers: list[int],
paragraph_line_boundaries: list[list[int]],
file_lines_list: list[str],
checker_dialog: CheckerDialog,
checker_dialog: JeebiesCheckerDialog,
order: str,
check_level: str,
) -> int:
Expand Down Expand Up @@ -464,12 +468,12 @@ def find_and_report_3_word_hebe_suspects(
paragraph_start_line_numbers: list[int],
paragraph_line_boundaries: list[list[int]],
file_lines_list: list[str],
checker_dialog: CheckerDialog,
checker_dialog: JeebiesCheckerDialog,
check_level: str,
) -> int:
"""Look for suspect "w1 be w2" or "w1 he w2" phrases in paragraphs."""

def make_dialog_line(info: str, checker_dialog: CheckerDialog) -> None:
def make_dialog_line(info: str, checker_dialog: JeebiesCheckerDialog) -> None:
"""Helper function for abstraction of repeated code."""

# We have the start position in the paragraph string of a suspect hebe.
Expand Down Expand Up @@ -618,7 +622,7 @@ def add_to_dialog(
line: str,
line_number: int,
hebe_start: int,
checker_dialog: CheckerDialog,
checker_dialog: JeebiesCheckerDialog,
) -> None:
"""Helper function that abstracts repeated code.
Expand Down Expand Up @@ -674,8 +678,10 @@ def process_jeebies(self, checker_entry: CheckerEntry) -> None:
"""Process the Jeebies query."""
if checker_entry.text_range is None:
return
start_mark = CheckerDialog.mark_from_rowcol(checker_entry.text_range.start)
end_mark = CheckerDialog.mark_from_rowcol(checker_entry.text_range.end)
start_mark = JeebiesCheckerDialog.mark_from_rowcol(
checker_entry.text_range.start
)
end_mark = JeebiesCheckerDialog.mark_from_rowcol(checker_entry.text_range.end)
match_text = maintext().get(start_mark, end_mark)
# Toggle match text
replacement_text = HB_TOGGLE[match_text[0]] + match_text[1]
Expand Down
8 changes: 6 additions & 2 deletions src/guiguts/tools/levenshtein.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ class LevenshteinEditDistance(StrEnum):
TWO = auto()


class LevenshteinCheckerDialog(CheckerDialog):
"""Minimal class to identify dialog type."""


class LevenshteinChecker:
"""Provide Levenshtein edit distance functionality."""

Expand Down Expand Up @@ -233,7 +237,7 @@ def unpack_and_write_to_dialog(result_tuple: tuple) -> None:
the required Levenshtein edit distance criteria. It calls utility
functions to generate the header line and select and highlight all
the file lines that make up the report for that word pair. It then
calls CheckerDialog methods to report the header line and all the
calls checker dialog methods to report the header line and all the
accompanying file lines.
Arg:
Expand Down Expand Up @@ -648,7 +652,7 @@ def distance_check_words() -> None:
return

# Create the checker dialog to show results
checker_dialog = CheckerDialog.show_dialog(
checker_dialog = LevenshteinCheckerDialog.show_dialog(
"Levenshtein Edit Distance Check",
rerun_command=lambda: levenshtein_check(project_dict),
)
Expand Down
9 changes: 7 additions & 2 deletions src/guiguts/tools/pptxt.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,15 @@
from guiguts.utilities import IndexRowCol, IndexRange
from guiguts.widgets import ToolTip


class PPtxtCheckerDialog(CheckerDialog):
"""Minimal class to identify dialog type."""


REPORT_LIMIT = 5 # Max number of times to report same issue for some checks

found_long_doctype_declaration: bool
checker_dialog: CheckerDialog
checker_dialog: PPtxtCheckerDialog
book: list[str]
word_list_map_count: dict[str, int]
word_list_map_lines: dict[str, list[int]]
Expand Down Expand Up @@ -2482,7 +2487,7 @@ def pptxt(project_dict: ProjectDict) -> None:
found_long_doctype_declaration = False

# Create the checker dialog to show results
checker_dialog = CheckerDialog.show_dialog(
checker_dialog = PPtxtCheckerDialog.show_dialog(
"PPtxt Results", rerun_command=lambda: pptxt(project_dict)
)
ToolTip(
Expand Down
6 changes: 6 additions & 0 deletions src/guiguts/widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,12 @@ def __init__(

grab_focus(self)

def __new__(cls, *args: Any, **kwargs: Any) -> "ToplevelDialog":
"""Ensure ToplevelDialogs are not instantiated directly."""
if cls is ToplevelDialog:
raise TypeError(f"only children of '{cls.__name__}' may be instantiated")
return object.__new__(cls)

@classmethod
def show_dialog(
cls: type[TlDlg],
Expand Down

0 comments on commit 4a5553c

Please sign in to comment.