Coverage for opt/mealie/lib/python3.12/site-packages/mealie/services/parser_services/parser_utils/string_utils.py: 16%

53 statements  

« prev     ^ index     » next       coverage.py v7.10.6, created at 2025-11-25 15:32 +0000

1import re 1a

2from fractions import Fraction 1a

3 

4compiled_match = re.compile(r"(.){1,6}\s\((.[^\(\)])+\)\s") 1a

5compiled_search = re.compile(r"\((.[^\(])+\)") 1a

6 

7 

8def move_parens_to_end(ing_str) -> str: 1a

9 """ 

10 Moves all parentheses in the string to the end of the string using Regex. 

11 If no parentheses are found, the string is returned unchanged. 

12 """ 

13 if re.match(compiled_match, ing_str): 13 ↛ 14line 13 didn't jump to line 14 because the condition on line 13 was never true

14 if match := re.search(compiled_search, ing_str): 

15 start = match.start() 

16 end = match.end() 

17 ing_str = ing_str[:start] + ing_str[end:] + " " + ing_str[start:end] 

18 

19 return ing_str 

20 

21 

22def check_char(char, *eql) -> bool: 1a

23 """Helper method to check if a characters matches any of the additional provided arguments""" 

24 return any(char == eql_char for eql_char in eql) 

25 

26 

27def convert_vulgar_fractions_to_regular_fractions(text: str) -> str: 1a

28 vulgar_fractions = { 

29 "¼": "1/4", 

30 "½": "1/2", 

31 "¾": "3/4", 

32 "⅐": "1/7", 

33 "⅑": "1/9", 

34 "⅒": "1/10", 

35 "⅓": "1/3", 

36 "⅔": "2/3", 

37 "⅕": "1/5", 

38 "⅖": "2/5", 

39 "⅗": "3/5", 

40 "⅘": "4/5", 

41 "⅙": "1/6", 

42 "⅚": "5/6", 

43 "⅛": "1/8", 

44 "⅜": "3/8", 

45 "⅝": "5/8", 

46 "⅞": "7/8", 

47 } 

48 

49 for vulgar_fraction, regular_fraction in vulgar_fractions.items(): 

50 # if we don't add a space in front of the fraction, mixed fractions will be broken 

51 # e.g. "1½" -> "11/2" 

52 text = text.replace(vulgar_fraction, f" {regular_fraction}").strip() 

53 

54 return text 

55 

56 

57def extract_quantity_from_string(source_str: str) -> tuple[float, str]: 1a

58 """ 

59 Extracts a quantity from a string. The quantity can be a fraction, decimal, or integer. 

60 

61 Returns the quantity and the remaining string. If no quantity is found, returns the quantity as 0. 

62 """ 

63 

64 source_str = source_str.strip() 

65 if not source_str: 

66 return 0, "" 

67 

68 source_str = convert_vulgar_fractions_to_regular_fractions(source_str) 

69 

70 mixed_fraction_pattern = re.compile(r"(\d+)\s+(\d+)/(\d+)") 

71 fraction_pattern = re.compile(r"(\d+)/(\d+)") 

72 number_pattern = re.compile(r"\d+(\.\d+)?") 

73 

74 try: 

75 # Check for a mixed fraction (e.g. "1 1/2") 

76 match = mixed_fraction_pattern.search(source_str) 

77 if match: 

78 whole_number = int(match.group(1)) 

79 numerator = int(match.group(2)) 

80 denominator = int(match.group(3)) 

81 quantity = whole_number + float(Fraction(numerator, denominator)) 

82 remaining_str = source_str[: match.start()] + source_str[match.end() :] 

83 

84 remaining_str = remaining_str.strip() 

85 return quantity, remaining_str 

86 

87 # Check for a fraction (e.g. "1/2") 

88 match = fraction_pattern.search(source_str) 

89 if match: 

90 numerator = int(match.group(1)) 

91 denominator = int(match.group(2)) 

92 quantity = float(Fraction(numerator, denominator)) 

93 remaining_str = source_str[: match.start()] + source_str[match.end() :] 

94 

95 remaining_str = remaining_str.strip() 

96 return quantity, remaining_str 

97 

98 # Check for a number (integer or float) 

99 match = number_pattern.search(source_str) 

100 if match: 

101 quantity = float(match.group()) 

102 remaining_str = source_str[: match.start()] + source_str[match.end() :] 

103 

104 remaining_str = remaining_str.strip() 

105 return quantity, remaining_str 

106 

107 except ZeroDivisionError: 

108 pass 

109 

110 # If no match, return 0 and the original string 

111 return 0, source_str