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