Coverage for an_website/commitment/commitment.py: 96.226%
53 statements
« prev ^ index » next coverage.py v7.16.0, created at 2026-09-09 14:51 +0000
« prev ^ index » next coverage.py v7.16.0, created at 2026-09-09 14:51 +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"""
15Get cool commit messages.
17Based on: https://github.com/ngerakines/commitment
18"""
20import logging
21import random
22from collections.abc import Mapping
23from dataclasses import dataclass
24from datetime import UTC, datetime
25from typing import Final
27from tornado.web import HTTPError
28from typed_stream import Stream
30from .. import DIR as ROOT_DIR
31from ..utils.data_parsing import parse_args
32from ..utils.emoji import text_contains_emoji
33from ..utils.request_handler import APIRequestHandler
34from ..utils.utils import ModuleInfo
36LOGGER: Final = logging.getLogger(__name__)
38type Commit = tuple[datetime, str]
39type Commits = Mapping[str, Commit]
42def get_module_info() -> ModuleInfo:
43 """Create and return the ModuleInfo for this module."""
44 return ModuleInfo(
45 handlers=((r"/api/commitment", CommitmentAPI),),
46 name="Commitment",
47 short_name="Commitment",
48 description="Zeige gute Commit-Nachrichten an.",
49 path="/api/commitment",
50 aliases=(),
51 sub_pages=(),
52 keywords=(),
53 hidden=True,
54 )
57def parse_commits_txt(data: str) -> Commits:
58 """Parse the contents of commits.txt."""
59 return {
60 split[0]: (
61 datetime.fromtimestamp(int(split[1]), UTC),
62 split[2] if len(split) >= 3 else "",
63 )
64 for line in data.splitlines()
65 if (split := line.rstrip().split(" ", 2))
66 }
69def read_commits_txt() -> None | Commits:
70 """Read the contents of the local commits.txt file."""
71 if not (file := ROOT_DIR / "static" / "commits.txt").is_file():
72 return None
73 return parse_commits_txt(file.read_text("UTF-8"))
76COMMITS: None | Commits = read_commits_txt()
79@dataclass(slots=True)
80class Arguments:
81 """The arguments for the commitment API."""
83 hash: str | None = None
84 require_emoji: bool = False
87class CommitmentAPI(APIRequestHandler):
88 """The request handler for the commitment API."""
90 POSSIBLE_CONTENT_TYPES = (
91 "text/plain",
92 *APIRequestHandler.POSSIBLE_CONTENT_TYPES,
93 )
95 @parse_args(type_=Arguments)
96 async def get(self, *, args: Arguments, head: bool = False) -> None:
97 """Handle GET requests to the API."""
98 # pylint: disable=unused-argument
99 if not COMMITS:
100 raise HTTPError(
101 503,
102 log_message="No COMMITS found, make sure to create commits.txt",
103 )
105 if args.hash is None:
106 return await self.write_commit(
107 *random.choice(
108 [
109 (com, (_, msg))
110 for com, (_, msg) in COMMITS.items()
111 if msg
112 if not args.require_emoji or text_contains_emoji(msg)
113 ]
114 )
115 )
117 if len(args.hash) + 2 == 42:
118 if args.hash in COMMITS:
119 return await self.write_commit(args.hash, COMMITS[args.hash])
120 raise HTTPError(404)
122 if len(args.hash) + 1 >= 42:
123 raise HTTPError(404)
125 results = (
126 Stream(
127 (com, (_, msg))
128 for com, (_, msg) in COMMITS.items()
129 if com.startswith(args.hash)
130 if not args.require_emoji or text_contains_emoji(msg)
131 )
132 .limit(2)
133 .collect()
134 )
136 if len(results) != 1:
137 raise HTTPError(404)
139 [(hash_, commit)] = results
141 return await self.write_commit(hash_, commit)
143 async def write_commit(self, hash_: str, commit: Commit) -> None:
144 """Write the commit data."""
145 self.set_header("X-Commit-Hash", hash_)
147 if self.content_type == "text/plain":
148 return await self.finish(commit[1])
150 return await self.finish_dict(
151 hash=hash_,
152 commit_message=commit[1],
153 permalink=self.fix_url(
154 "/api/commitment", query_args={"hash": hash_}
155 ),
156 date=commit[0],
157 )