diff --git a/crossword_puzzle/errors.py b/crossword_puzzle/errors.py index 728f4a1ba..cb806950f 100644 --- a/crossword_puzzle/errors.py +++ b/crossword_puzzle/errors.py @@ -1,6 +1,4 @@ -class CrosswordBaseError(Exception): ... # Useful in error handling to see if - # an error is related to crossword - # generation +class CrosswordBaseError(Exception): ... class EmptyDefinitions(CrosswordBaseError): diff --git a/crossword_puzzle/main.py b/crossword_puzzle/main.py index b342d1d5d..129f0954a 100644 --- a/crossword_puzzle/main.py +++ b/crossword_puzzle/main.py @@ -41,7 +41,6 @@ ) from crossword_puzzle.cword_gen import Crossword from crossword_puzzle.cword_webapp.app import _create_app_process, terminate_app -from crossword_puzzle.errors import CrosswordBaseError from crossword_puzzle.utils import ( _get_colour_palette_for_webapp, _get_language_options, @@ -49,6 +48,8 @@ _update_config, find_best_crossword, load_definitions, + _make_cword_info_json, + _make_category_info_json ) @@ -525,7 +526,7 @@ def terminate_cword_webapp(self) -> None: terminate_app() def _rollback_states(self) -> None: - # User might not have selected a crossword within their category, so + # User might not have selected a crossword within their category, so # this if condition is requried. if hasattr(self, "selected_category_object"): self.selected_category_object.b_close_category.configure( @@ -593,11 +594,8 @@ def load_selected_cword(self) -> None: ) ) except Exception as ex: - if issubclass(type(ex), CrosswordBaseError): - print(f"{type(ex).__name__}: {ex}") - return AppHelper.show_messagebox(cword_gen_err=True) - else: - return print(f"{type(ex).__name__}: {ex}") + print(f"{type(ex).__name__}: {ex}") + return AppHelper.show_messagebox(cword_gen_err=True) # Only modify states after error checking has been conducted self.b_load_selected_cword.configure(state="disabled") @@ -724,6 +722,13 @@ def _generate_crossword_category_blocks(self) -> None: for category in [ f for f in scandir(Paths.BASE_CWORDS_PATH) if f.is_dir() ]: + # Make the ``info.json`` file if it doesn't exist already + if ( + "info.json" not in listdir(category.path) + or path.getsize(path.join(category.path, "info.json")) <= 0 + ): + _make_category_info_json(path.join(category.path, "info.json")) + block = CrosswordCategoryBlock( self.info_block_container, self, category.name, i ) @@ -816,6 +821,8 @@ def __init__( self.category = category self.value = value + # Represents the currently selected crossword radiobutton selector + # within the crosswords of this category (once open) self.selected_block = IntVar() self.selected_block.set(-1) @@ -867,10 +874,15 @@ def _place_content(self) -> None: ) def _get_colour_tag_hex(self) -> str: + """Get the hex colour of the bottom tag of this category, read from + its ``info.json`` file.""" with open( path.join(Paths.BASE_CWORDS_PATH, self.category, "info.json") ) as file: - return load(file)["bottom_tag_colour"] + try: + return load(file)["bottom_tag_colour"] + except: + return "#abcdef" def _sort_category_content(self, arr: list[str]) -> list[str]: """Sort the cword content of a category by the cword suffixes (-easy @@ -880,10 +892,11 @@ def _sort_category_content(self, arr: list[str]) -> list[str]: return sorted( arr, key=lambda i: CrosswordDifficulties.DIFFICULTIES.index( - i.split("-")[-1].capitalize() + i.name.split("-")[-1].capitalize() ), ) - except Exception: + except Exception: # Could not find the "-" in the crossword name, so + # don't sort this category return arr def _configure_cword_blocks_state( @@ -898,6 +911,8 @@ def _configure_cword_blocks_state( def _view_category(self) -> None: """View all crossword info blocks for a specific category.""" self.b_view_category.configure(state="disabled") + # Reset crossword canvas xview so it is at the beginning + self.master.info_block_container._parent_canvas.xview("moveto", 0.0) for block in self.master.category_block_objects: # Remove all category # blocks block.pack_forget() @@ -909,16 +924,24 @@ def _view_category(self) -> None: self.cword_block_objects: list[CrosswordInfoBlock] = [] # Gather all crossword directories with an info.json file. crosswords = [ - f.name + f for f in scandir(path.join(Paths.BASE_CWORDS_PATH, self.category)) - if f.is_dir() and "info.json" in listdir(f.path) + if f.is_dir() + and "definitions.json" in listdir(f.path) + and path.getsize(path.join(f.path, "definitions.json")) > 0 ] + i: int = 1 for cword in self._sort_category_content(crosswords): + # Make the ``info.json`` file if it doesn't exist already + info_path = path.join(cword.path, "info.json") + if not path.exists(info_path) or path.getsize(info_path) <= 0: + _make_cword_info_json(cword.path, cword.name, self.category) + block = CrosswordInfoBlock( self.master.info_block_container, self.master, - cword, + cword.name, self.category, self, i, diff --git a/crossword_puzzle/utils.py b/crossword_puzzle/utils.py index 7d39b52e9..584d2b81d 100644 --- a/crossword_puzzle/utils.py +++ b/crossword_puzzle/utils.py @@ -3,12 +3,13 @@ from __future__ import annotations from configparser import ConfigParser -from json import load +from json import load, dump from os import path, scandir +from random import randint from babel import Locale -from crossword_puzzle.constants import Colour, Paths +from crossword_puzzle.constants import Colour, Paths, CrosswordDifficulties def _update_config( @@ -154,5 +155,50 @@ def _load_attempts_db() -> dict[str, int]: integral to the crossword optimisation process, as crossword generation time scales logarithmically with word count. """ + with open(Paths.ATTEMPTS_DB_PATH) as file: return load(file) + + +def _make_cword_info_json(path_, cword_name, category) -> None: + """Make an info.json file for a given crossword since it does not exist. + Makes it easier for the end-user to make their own crossword if they + really want to. + """ + + with open(path.join(path_, "info.json"), "w") as info_obj, \ + open(path.join(path_, "definitions.json"), "r") as def_obj: + total_definitions: int = len(load(def_obj)) + + # Infer the difficulty and crossword name if possible + try: + parsed_cword_name_components = path.basename(path_).split("-") + difficulty: int = CrosswordDifficulties.DIFFICULTIES.index( + parsed_cword_name_components[-1].title() + ) + adjusted_cword_name = "-".join(parsed_cword_name_components[0:-1]) + except Exception: + difficulty: int = 0 + adjusted_cword_name = cword_name + + return dump( + { + "total_definitions": total_definitions, + "difficulty": difficulty, + "symbol": "0x2717", + "name": adjusted_cword_name, + "translated_name": "", + "category": category, + }, + info_obj, + indent=4, + ) + + +def _make_category_info_json(path_) -> None: + """Write a new info.json to a category since it does not exist in the a + category's directory. + """ + hex_ = "#%06X" % randint(0, 0xFFFFFF) + with open(path_, "w") as f: + return dump({"bottom_tag_colour": hex_}, f, indent=4)