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:48 +0000
« prev ^ index » next coverage.py v7.10.6, created at 2025-11-25 15:48 +0000
1import re 1a
2from fractions import Fraction 1a
4compiled_match = re.compile(r"(.){1,6}\s\((.[^\(\)])+\)\s") 1a
5compiled_search = re.compile(r"\((.[^\(])+\)") 1a
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]
19 return ing_str
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)
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 }
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()
54 return text
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.
61 Returns the quantity and the remaining string. If no quantity is found, returns the quantity as 0.
62 """
64 source_str = source_str.strip()
65 if not source_str:
66 return 0, ""
68 source_str = convert_vulgar_fractions_to_regular_fractions(source_str)
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+)?")
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() :]
84 remaining_str = remaining_str.strip()
85 return quantity, remaining_str
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() :]
95 remaining_str = remaining_str.strip()
96 return quantity, remaining_str
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() :]
104 remaining_str = remaining_str.strip()
105 return quantity, remaining_str
107 except ZeroDivisionError:
108 pass
110 # If no match, return 0 and the original string
111 return 0, source_str