Coverage for src/git_dag/utils.py: 93%

25 statements  

« prev     ^ index     » next       coverage.py v7.8.0, created at 2025-04-08 12:49 +0200

1"""Misc utils.""" 

2 

3import codecs 

4import re 

5from datetime import datetime, timedelta 

6 

7 

8def escape_decode(text: str) -> str: 

9 """Decode escapes of escapes (e.g., ``\\\\n -> \\n``). 

10 

11 Note 

12 ----- 

13 The approach in https://stackoverflow.com/a/37059682 is used because it handles 

14 unicode characters. FIXME: unfortunately, it relies on the internal function 

15 ``codecs.escape_decode`` (https://github.com/python/cpython/issues/74773). 

16 

17 """ 

18 return codecs.escape_decode(text.encode())[0].decode() # type: ignore 

19 

20 

21def transform_ascii_control_chars(text: str) -> str: 

22 """Transform ascii control characters. 

23 

24 Note 

25 ----- 

26 This is necessary because SVGs exported from graphviz cannot be displayed when they 

27 contain certain ascii control characters. 

28 

29 """ 

30 

31 def ascii_to_caret_notation(match: re.Match[str]) -> str: 

32 char = match.group(0) 

33 return f"^{chr(ord(char) + 64)}" 

34 

35 # do not transform \a \b \t \n \v \f \r (which correspond to ^G-^M) 

36 # https://en.wikipedia.org/wiki/ASCII#Control_code_table 

37 return re.sub(r"[\x01-\x06\x0E-\x1A]", ascii_to_caret_notation, text) 

38 

39 

40def creator_timestamp_format( 

41 data: str, fmt: str = "%a %b %d %H:%M:%S %Y" 

42) -> tuple[str, str, str]: 

43 """Format a creator (author/committer) and timestamp. 

44 

45 Note 

46 ----- 

47 The default format (``fmt``) is the same as the default format used by git. 

48 

49 """ 

50 

51 def formatter(timestamp_timezone: str) -> str: 

52 """Convert a string containing a timestamp and maybe a timezone.""" 

53 split = timestamp_timezone.split() 

54 date_time = datetime.fromtimestamp(int(split[0])).strftime(fmt) 

55 return f"{date_time} {split[1]}" if len(split) == 2 else date_time 

56 

57 match = re.search("(?P<name>.*) (?P<email><.*>) (?P<date>.*)", data) 

58 if match: 

59 creator = match.group("name") 

60 email = match.group("email") 

61 date = formatter(match.group("date")) 

62 return creator, email, date 

63 

64 raise ValueError("Creator pattern not matched.") 

65 

66 

67def increase_date( 

68 date: str, 

69 hours: int = 1, 

70 date_format: str = "%d/%m/%y %H:%M %z", 

71) -> str: 

72 """Increase a date by a given number of hours.""" 

73 date_obj = datetime.strptime(date, date_format) 

74 return (date_obj + timedelta(hours=hours)).strftime(date_format)