Coverage for tools/sel_tools/diff_creation/report.py: 89%

47 statements  

« prev     ^ index     » next       coverage.py v7.10.6, created at 2025-09-02 05:55 +0000

1"""Git diff report.""" 

2 

3from dataclasses import dataclass 

4from pathlib import Path 

5 

6import pandas as pd 

7from pygments import highlight 

8from pygments.formatters import HtmlFormatter 

9from pygments.lexers.diff import DiffLexer 

10 

11MD_WARNING_REPORT = """# Inactive Student Repositories 

12 

13The following student repositories have not been updated since the last homework evaluation: 

14 

15%s 

16 

17Please check if the students are still active. 

18If not, remove them from the course and the repo config for the current semester. 

19The latter to make sure they are not evaluated again next time. 

20""" 

21 

22 

23@dataclass 

24class Diff: 

25 """Git diff model.""" 

26 

27 hexsha: str 

28 author: str 

29 message: str 

30 patch: str 

31 

32 

33@dataclass 

34class DiffReport: 

35 """Git diff model.""" 

36 

37 def __init__(self, repo_path: Path, diffs: list[Diff]) -> None: 

38 self.repo_path = repo_path 

39 self.diffs = diffs 

40 

41 @property 

42 def has_diffs(self) -> bool: 

43 return bool(self.diffs) 

44 

45 def generate_overview_table(self) -> pd.DataFrame: 

46 return pd.DataFrame( 

47 [ 

48 { 

49 "hexsha": diff.hexsha, 

50 "author": diff.author, 

51 "message": diff.message, 

52 } 

53 for diff in self.diffs 

54 ] 

55 ) 

56 

57 def write_diff_patches(self) -> None: 

58 for index, diff in enumerate(self.diffs): 

59 base_path = self.repo_path / f"{index}-{diff.hexsha}" 

60 try: 

61 base_path.with_suffix(".patch").write_text(diff.patch, encoding="utf-8", errors="replace") 

62 base_path.with_suffix(".html").write_text( 

63 self.highlight_diff(diff.patch), encoding="utf-8", errors="replace" 

64 ) 

65 except UnicodeError: 

66 print("UnicodeError: Fallback: clean the patch text by encoding/decoding to remove surrogates") 

67 clean_patch = diff.patch.encode("utf-8", errors="replace").decode("utf-8", errors="replace") 

68 base_path.with_suffix(".patch").write_text(clean_patch) 

69 base_path.with_suffix(".html").write_text(self.highlight_diff(clean_patch)) 

70 

71 @staticmethod 

72 def highlight_diff(patch: str) -> str: 

73 return str(highlight(patch, DiffLexer(), HtmlFormatter(full=True, style="manni"))) 

74 

75 

76def write_diff_reports(reports: list[DiffReport], report_base_name: str) -> None: 

77 """Write diff reports to disk.""" 

78 for report in reports: 

79 print(f"Writing diff report for {report.repo_path.name}") 

80 report.generate_overview_table().to_csv(report.repo_path.joinpath(report_base_name).with_suffix(".csv")) 

81 report.write_diff_patches() 

82 

83 

84def write_report_for_inactive_student_repos(reports: list[DiffReport], workspace: Path) -> None: 

85 """Check for repos that don't have any diffs since the last homework and write them to a file.""" 

86 inactive_repos = [f"- {report.repo_path.name}" for report in reports if not report.has_diffs] 

87 print(f"Found {len(inactive_repos)} inactive student repositories.") 

88 if inactive_repos: 

89 workspace.joinpath("inactive_student_repos.md").write_text(MD_WARNING_REPORT % "\n".join(inactive_repos))