1"""Utility functions for advanced search.

2

3 This module provides helper functions for content processing, time parsing,

4 and other search-related utilities.

5"""

6

7 from __future__ import annotations

8

9 from contextlib import suppress

10 from datetime import UTC, datetime, timedelta

11

12 from session_buddy.session_types import TimeRange

13 from session_buddy.utils.regex_patterns import SAFE_PATTERNS

14

15

16 def extract_technical_terms(content: str) -> list[str]:

17 """Extract technical terms and patterns from content."""

18 terms = []

19

20 # Programming language detection

21 lang_pattern_names = [

22 "python_code",

23 "javascript_code",

24 "sql_code",

25 "error_keywords",

26 ]

27 lang_mapping = {

28 "python_code": "python",

29 "javascript_code": "javascript",

30 "sql_code": "sql",

31 "error_keywords": "error",

32 }

33

34 for pattern_name in lang_pattern_names:

35 pattern = SAFE_PATTERNS[pattern_name]

36 if pattern.search(content):

37 terms.append(lang_mapping[pattern_name])

38

39 # Extract function names

40 func_pattern = SAFE_PATTERNS["function_definition"]

41 func_matches = func_pattern.findall(content)

42 terms.extend([f"function:{func}" for func in func_matches[:5]]) # Limit to 5

43

44 # Extract class names

45 class_pattern = SAFE_PATTERNS["class_definition"]

46 class_matches = class_pattern.findall(content)

47 terms.extend([f"class:{cls}" for cls in class_matches[:5]])

48

49 # Extract file extensions

50 ext_pattern = SAFE_PATTERNS["file_extension"]

51 file_matches = ext_pattern.findall(content)

52 terms.extend([f"filetype:{ext}" for ext in set(file_matches[:10])])

53

54 return terms[:20] # Limit total terms

55

56

57 def truncate_content(content: str, max_length: int = 500) -> str:

58 """Truncate content to maximum length."""

59 return content[:max_length] + "..." if len(content) > max_length else content

60

61

62 def ensure_timezone(timestamp: datetime) -> datetime:

63 """Ensure timestamp has timezone information."""

64 return timestamp.replace(tzinfo=UTC) if timestamp.tzinfo is None else timestamp

65

66

67 def parse_timeframe_single(timeframe: str) -> datetime | None:

68 """Parse timeframe string into datetime."""

69 with suppress(ValueError):

70 if timeframe.endswith("d"): 70 ↛ 73 line 70 didn't jump to line 73 because the condition on line 70 was always true

71 days = int(timeframe[:-1])

72 return datetime.now(UTC) - timedelta(days=days)

73 if timeframe.endswith("h"):

74 hours = int(timeframe[:-1])

75 return datetime.now(UTC) - timedelta(hours=hours)

76 if timeframe.endswith("w"):

77 weeks = int(timeframe[:-1])

78 return datetime.now(UTC) - timedelta(weeks=weeks)

79 if timeframe.endswith("m"):

80 months = int(timeframe[:-1])

81 return datetime.now(UTC) - timedelta(days=months * 30)

82 return None

83

84

85 def parse_timeframe(timeframe: str) -> TimeRange:

86 """Parse timeframe string into TimeRange object.

87

88 Supports formats like:

89 - '7d' (last 7 days)

90 - '2024-01' (specific month)

91 - '2024' (specific year)

92 - '2024-01-01..2024-01-31' (date range)

93 """

94 # Range format: 'start..end'

95 if ".." in timeframe: 95 ↛ 96 line 95 didn't jump to line 96 because the condition on line 95 was never true

96 parts = timeframe.split("..")

97 start = datetime.fromisoformat(parts[0]).replace(tzinfo=UTC)

98 end = datetime.fromisoformat(parts[1]).replace(tzinfo=UTC)

99 return TimeRange(start=start, end=end)

100

101 # Relative timeframe: '7d', '2w', etc.

102 if timeframe[-1] in "dhwm": 102 ↛ 109 line 102 didn't jump to line 109 because the condition on line 102 was always true

103 end = datetime.now(UTC)

104 relative_start: datetime | None = parse_timeframe_single(timeframe)

105 if relative_start: 105 ↛ 109 line 105 didn't jump to line 109 because the condition on line 105 was always true

106 return TimeRange(start=relative_start, end=end)

107

108 # Year only: '2024'

109 if len(timeframe) == 4 and timeframe.isdigit():

110 year = int(timeframe)

111 start = datetime(year, 1, 1, tzinfo=UTC)

112 end = datetime(year + 1, 1, 1, tzinfo=UTC)

113 return TimeRange(start=start, end=end)

114

115 # Year-month: '2024-01'

116 if len(timeframe) == 7:

117 year, month = map(int, timeframe.split("-"))

118 start = datetime(year, month, 1, tzinfo=UTC)

119 # Calculate next month

120 if month == 12:

121 end = datetime(year + 1, 1, 1, tzinfo=UTC)

122 else:

123 end = datetime(year, month + 1, 1, tzinfo=UTC)

124 return TimeRange(start=start, end=end)

125

126 # Default: last 7 days

127 end = datetime.now(UTC)

128 start = end - timedelta(days=7)

129 return TimeRange(start=start, end=end)