Coverage for an_website/commitment/commitment.py: 96.226%
53 statements
« prev ^ index » next coverage.py v7.14.1, created at 2026-06-10 18:56 +0000
« prev ^ index » next coverage.py v7.14.1, created at 2026-06-10 18: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"""
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
27import emoji
28from tornado.web import HTTPError
29from typed_stream import Stream
31from .. import DIR as ROOT_DIR
32from ..utils.data_parsing import parse_args
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]: (datetime.fromtimestamp(int(split[1]), UTC), split[2])
61 for line in data.splitlines()
62 if (split := line.rstrip().split(" ", 2))
63 }
66def read_commits_txt() -> None | Commits:
67 """Read the contents of the local commits.txt file."""
68 if not (file := ROOT_DIR / "static" / "commits.txt").is_file():
69 return None
70 return parse_commits_txt(file.read_text("UTF-8"))
73COMMITS: None | Commits = read_commits_txt()
76@dataclass(slots=True)
77class Arguments:
78 """The arguments for the commitment API."""
80 hash: str | None = None
81 require_emoji: bool = False
84class CommitmentAPI(APIRequestHandler):
85 """The request handler for the commitment API."""
87 POSSIBLE_CONTENT_TYPES = (
88 "text/plain",
89 *APIRequestHandler.POSSIBLE_CONTENT_TYPES,
90 )
92 @parse_args(type_=Arguments)
93 async def get(self, *, args: Arguments, head: bool = False) -> None:
94 """Handle GET requests to the API."""
95 # pylint: disable=unused-argument
96 if not COMMITS:
97 raise HTTPError(
98 503,
99 log_message="No COMMITS found, make sure to create commits.txt",
100 )
102 if args.hash is None:
103 return await self.write_commit(
104 *random.choice(
105 [
106 (com, (_, msg))
107 for com, (_, msg) in COMMITS.items()
108 if not args.require_emoji or any(emoji.analyze(msg))
109 ]
110 )
111 )
113 if len(args.hash) + 2 == 42:
114 if args.hash in COMMITS:
115 return await self.write_commit(args.hash, COMMITS[args.hash])
116 raise HTTPError(404)
118 if len(args.hash) + 1 >= 42:
119 raise HTTPError(404)
121 results = (
122 Stream(
123 (com, (_, msg))
124 for com, (_, msg) in COMMITS.items()
125 if com.startswith(args.hash)
126 if not args.require_emoji or any(emoji.analyze(msg))
127 )
128 .limit(2)
129 .collect()
130 )
132 if len(results) != 1:
133 raise HTTPError(404)
135 [(hash_, commit)] = results
137 return await self.write_commit(hash_, commit)
139 async def write_commit(self, hash_: str, commit: Commit) -> None:
140 """Write the commit data."""
141 self.set_header("X-Commit-Hash", hash_)
143 if self.content_type == "text/plain":
144 return await self.finish(commit[1])
146 return await self.finish_dict(
147 hash=hash_,
148 commit_message=commit[1],
149 permalink=self.fix_url(
150 "/api/commitment", query_args={"hash": hash_}
151 ),
152 date=commit[0],
153 )