Coverage for app/venv/lib/python3.14/site-packages/weblate/checks/utils.py: 29%

53 statements  

« prev     ^ index     » next       coverage.py v7.15.2, created at 2026-10-07 07:15 +0000

1# Copyright © Michal Čihař <michal@weblate.org> 

2# 

3# SPDX-License-Identifier: GPL-3.0-or-later 

4 

5from __future__ import annotations 

6 

7from typing import TYPE_CHECKING 

8 

9from weblate.checks.models import CHECKS 

10 

11if TYPE_CHECKING: 11 ↛ 12line 11 didn't jump to line 12 because the condition on line 11 was never true

12 from collections.abc import Generator 

13 

14 from weblate.trans.models import Unit 

15 

16 

17def highlight_pygments(source: str, unit: Unit) -> Generator[tuple[int, int, str]]: 

18 """ 

19 Highlight syntax characters using pygments. 

20 

21 This is not really a full syntax highlighting, we're only interested in 

22 non-translatable strings. 

23 """ 

24 from pygments.lexers.markup import RstLexer 

25 from pygments.token import Token 

26 

27 if "rst-text" in unit.all_flags: 

28 lexer = RstLexer(stripnl=False) 

29 start = 0 

30 for token, text in lexer.get_tokens(source): 

31 if token == Token.Literal.String: 

32 if text[0] == "`" and text != "`_": 

33 yield ((start, start + 1, "`")) 

34 else: 

35 yield ((start, start + len(text), text)) 

36 elif token == Token.Literal.String.Interpol: 

37 yield ((start, start + len(text), text)) 

38 elif token == Token.Generic.Strong: 

39 end = start + len(text) 

40 yield (start, start + 2, "**") 

41 yield (end - 2, end, "**") 

42 elif token == Token.Generic.Emph: 

43 end = start + len(text) 

44 yield (start, start + 1, "*") 

45 yield (end - 1, end, "*") 

46 start += len(text) 

47 

48 

49def highlight_string( 

50 source: str, unit: Unit, *, highlight_syntax: bool = False 

51) -> list[tuple[int, int, str]]: 

52 """Return highlights for a string.""" 

53 if unit is None: 53 ↛ 54line 53 didn't jump to line 54 because the condition on line 53 was never true

54 return [] 

55 highlights = [] 

56 for check in CHECKS: 

57 if not CHECKS[check].target: 

58 continue 

59 highlights.extend(CHECKS[check].check_highlight(source, unit)) 

60 

61 if highlight_syntax: 61 ↛ 62line 61 didn't jump to line 62 because the condition on line 61 was never true

62 highlights.extend(highlight_pygments(source, unit)) 

63 

64 # Remove empty strings 

65 highlights = [highlight for highlight in highlights if highlight[2]] 

66 

67 # Sort by order in string, longest first 

68 highlights.sort(key=lambda item: (item[0], -item[1])) 

69 

70 # Remove overlapping ones 

71 for hl_idx in range(len(highlights)): 71 ↛ 72line 71 didn't jump to line 72 because the loop on line 71 never started

72 if hl_idx >= len(highlights): 

73 break 

74 elref = highlights[hl_idx] 

75 hl_idx_next = hl_idx + 1 

76 while hl_idx_next < len(highlights): 

77 eltest = highlights[hl_idx_next] 

78 if eltest[0] >= elref[0] and eltest[1] <= elref[1]: 

79 # Elements overlap, remove inner one 

80 highlights.pop(hl_idx_next) 

81 # Do not increment index here as we've removed the current element 

82 elif eltest[0] > elref[1]: 

83 # This is not an overlapping element 

84 break 

85 else: 

86 # Increase index to test 

87 hl_idx_next += 1 

88 

89 return highlights