Coverage for an_website/hangman_solver/hangman_solver.py: 78.947%
57 statements
« prev ^ index » next coverage.py v7.6.4, created at 2024-11-16 19:56 +0000
« prev ^ index » next coverage.py v7.6.4, created at 2024-11-16 19:56 +0000
1# This program is free software: you can redistribute it and/or modify
2# it under the terms of the GNU Affero General Public License as
3# published by the Free Software Foundation, either version 3 of the
4# License, or (at your option) any later version.
5#
6# This program is distributed in the hope that it will be useful,
7# but WITHOUT ANY WARRANTY; without even the implied warranty of
8# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9# GNU Affero General Public License for more details.
10#
11# You should have received a copy of the GNU Affero General Public License
12# along with this program. If not, see <https://www.gnu.org/licenses/>.
14"""A page that helps solving hangman puzzles."""
16from __future__ import annotations
18from dataclasses import dataclass
20from hangman_solver import (
21 HangmanResult,
22 Language,
23 UnknownLanguageError,
24 read_words_with_length,
25 solve,
26 solve_crossword,
27)
28from tornado.web import HTTPError
30from ..utils.base_request_handler import BaseRequestHandler
31from ..utils.data_parsing import parse_args
32from ..utils.request_handler import APIRequestHandler, HTMLRequestHandler
33from ..utils.utils import ModuleInfo
36def get_module_info() -> ModuleInfo:
37 """Create and return the ModuleInfo for this module."""
38 languages = "|".join([lang.value.lower() for lang in Language.values()])
39 return ModuleInfo(
40 handlers=(
41 (r"/hangman-loeser", HangmanSolver),
42 (r"/api/hangman-loeser", HangmanSolverAPI),
43 (
44 rf"/hangman-loeser/worte/({languages})/([1-9]\d*).txt",
45 HangmanSolverWords,
46 ),
47 ),
48 name="Hangman-Löser",
49 description="Eine Webseite, die Lösungen für Galgenmännchen findet",
50 path="/hangman-loeser",
51 keywords=("Galgenmännchen", "Hangman", "Löser", "Solver", "Worte"),
52 aliases=(
53 "/hangman-l%C3%B6ser",
54 "/hangman-löser",
55 "/hangman-solver",
56 ),
57 )
60@dataclass(slots=True)
61class HangmanArguments:
62 """Arguments for the hangman solver."""
64 max_words: int = 20
65 crossword_mode: bool = False
66 lang: str = "de"
67 input: str = ""
68 invalid: str = ""
70 def get_max_words(self) -> int:
71 """Return the maximum number of words."""
72 return max(0, min(100, self.max_words))
75def solve_hangman(data: HangmanArguments) -> HangmanResult:
76 """Generate a hangman object based on the input and return it."""
77 try:
78 lang = Language.parse_string(data.lang)
79 except UnknownLanguageError as err:
80 raise HTTPError(
81 400, reason=f"{data.lang!r} is an invalid language"
82 ) from err
84 return (solve_crossword if data.crossword_mode else solve)(
85 data.input, list(data.invalid), lang, data.get_max_words()
86 )
89class HangmanSolver(HTMLRequestHandler):
90 """Request handler for the hangman solver page."""
92 RATELIMIT_GET_LIMIT = 10
94 @parse_args(type_=HangmanArguments, name="data")
95 async def get(self, *, data: HangmanArguments, head: bool = False) -> None:
96 """Handle GET requests to the hangman solver page."""
97 if head:
98 return
100 await self.render(
101 "pages/hangman_solver.html",
102 hangman_result=solve_hangman(data),
103 data=data,
104 )
107class HangmanSolverAPI(APIRequestHandler, HangmanSolver):
108 """Request handler for the hangman solver API."""
110 RATELIMIT_GET_LIMIT = 10
112 @parse_args(type_=HangmanArguments, name="data")
113 async def get(self, *, data: HangmanArguments, head: bool = False) -> None:
114 """Handle GET requests to the hangman solver API."""
115 if head:
116 return
117 hangman_result = solve_hangman(data)
118 await self.finish(
119 {
120 "input": hangman_result.input,
121 "invalid": "".join(hangman_result.invalid),
122 "words": hangman_result.words,
123 "word_count": hangman_result.matching_words_count,
124 "letters": dict(hangman_result.letter_frequency),
125 "crossword_mode": data.crossword_mode,
126 "max_words": data.get_max_words(),
127 "lang": hangman_result.language.value,
128 }
129 )
132class HangmanSolverWords(BaseRequestHandler):
133 """Request handler for the hangman word lists."""
135 RATELIMIT_GET_LIMIT = 15
136 POSSIBLE_CONTENT_TYPES = ("text/plain",)
138 async def get(
139 self, language: str, length: str, *, head: bool = False
140 ) -> None:
141 """Handle GET requests to the hangman solver page."""
142 # pylint: disable=unused-argument
143 try:
144 lang = Language.parse_string(language)
145 except UnknownLanguageError as err:
146 raise HTTPError(404) from err
148 try:
149 word_length = int(length)
150 except ValueError as err:
151 raise HTTPError(404) from err
153 for word in read_words_with_length(lang, word_length):
154 self.write(word)
155 self.write(b"\n")
157 await self.finish()