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

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/>. 

13 

14""" 

15Get cool commit messages. 

16 

17Based on: https://github.com/ngerakines/commitment 

18""" 

19 

20import logging 

21import random 

22from collections.abc import Mapping 

23from dataclasses import dataclass 

24from datetime import UTC, datetime 

25from typing import Final 

26 

27import emoji 

28from tornado.web import HTTPError 

29from typed_stream import Stream 

30 

31from .. import DIR as ROOT_DIR 

32from ..utils.data_parsing import parse_args 

33from ..utils.request_handler import APIRequestHandler 

34from ..utils.utils import ModuleInfo 

35 

36LOGGER: Final = logging.getLogger(__name__) 

37 

38type Commit = tuple[datetime, str] 

39type Commits = Mapping[str, Commit] 

40 

41 

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 ) 

55 

56 

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 } 

64 

65 

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")) 

71 

72 

73COMMITS: None | Commits = read_commits_txt() 

74 

75 

76@dataclass(slots=True) 

77class Arguments: 

78 """The arguments for the commitment API.""" 

79 

80 hash: str | None = None 

81 require_emoji: bool = False 

82 

83 

84class CommitmentAPI(APIRequestHandler): 

85 """The request handler for the commitment API.""" 

86 

87 POSSIBLE_CONTENT_TYPES = ( 

88 "text/plain", 

89 *APIRequestHandler.POSSIBLE_CONTENT_TYPES, 

90 ) 

91 

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 ) 

101 

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 ) 

112 

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) 

117 

118 if len(args.hash) + 1 >= 42: 

119 raise HTTPError(404) 

120 

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 ) 

131 

132 if len(results) != 1: 

133 raise HTTPError(404) 

134 

135 [(hash_, commit)] = results 

136 

137 return await self.write_commit(hash_, commit) 

138 

139 async def write_commit(self, hash_: str, commit: Commit) -> None: 

140 """Write the commit data.""" 

141 self.set_header("X-Commit-Hash", hash_) 

142 

143 if self.content_type == "text/plain": 

144 return await self.finish(commit[1]) 

145 

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 )