Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/mistune/_inline/links.py: 99%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

110 statements  

1from __future__ import annotations 

2 

3from dataclasses import dataclass 

4from typing import TYPE_CHECKING, Any, Dict, Match, Optional, Tuple 

5 

6from ..core import InlineState 

7from ..helpers import MAX_LINK_LABEL_LENGTH, parse_link, parse_link_label 

8from ..util import unikey 

9 

10if TYPE_CHECKING: 

11 from ..inline_parser import InlineParser 

12 

13 

14@dataclass(slots=True) 

15class Bracket: 

16 start: int 

17 token_index: int 

18 image: bool 

19 previous_link: Optional[Bracket] = None 

20 active: bool = True 

21 nested: bool = False 

22 child_depth: int = 0 

23 

24 

25def parse_link_open(inline: InlineParser, m: Match[str], state: InlineState) -> int: 

26 if state.brackets: 

27 state.brackets[-1].nested = True 

28 bracket = Bracket(m.end(), len(state.tokens), m.group(0).startswith("!"), state.last_link) 

29 if not bracket.image: 

30 state.last_link = bracket 

31 state.append_token({"type": "text", "raw": m.group(0)}) 

32 state.brackets.append(bracket) 

33 return m.end() 

34 

35 

36def parse_link_close(inline: InlineParser, m: Match[str], state: InlineState) -> int: 

37 pos = m.start() 

38 if not state.env.get("ref_links"): 

39 # Without references, only a ']' immediately followed by '(' can 

40 # produce a link. Retire the other openers without parsing candidates. 

41 literal_end = m.end() - int(state.src.startswith("(", m.end())) 

42 if literal_end > pos: 

43 _discard_brackets(state, literal_end - pos) 

44 inline.process_text(state.src[pos:literal_end], state) 

45 pos = literal_end 

46 while pos < m.end(): 

47 if not state.brackets: 

48 inline.process_text(state.src[pos : m.end()], state) 

49 return m.end() 

50 pos = _close_bracket(inline, state, pos) 

51 return pos 

52 

53 

54def _discard_brackets(state: InlineState, count: int) -> None: 

55 if count >= len(state.brackets): 

56 state.brackets.clear() 

57 state.last_link = None 

58 return 

59 discarded = state.brackets[-count:] 

60 del state.brackets[-count:] 

61 parent = state.brackets[-1] 

62 found_link = False 

63 for bracket in discarded: 

64 parent.child_depth = max(parent.child_depth, bracket.child_depth) 

65 if not bracket.image and not found_link: 

66 state.last_link = bracket.previous_link 

67 found_link = True 

68 

69 

70def _close_bracket(inline: InlineParser, state: InlineState, close: int) -> int: 

71 opener = state.brackets.pop() 

72 if not opener.image: 

73 state.last_link = opener.previous_link 

74 if state.brackets: 

75 state.brackets[-1].child_depth = max(state.brackets[-1].child_depth, opener.child_depth) 

76 if not opener.active or (state.in_link and not opener.image): 

77 inline.process_text("]", state) 

78 return close + 1 

79 

80 # A rejected outer wrapper must not repeatedly copy an already deep subtree. 

81 if state.depth + opener.child_depth >= inline.max_nested_level: 

82 inline.process_text("]", state) 

83 return close + 1 

84 

85 attrs: Optional[Dict[str, Any]] = None 

86 end = close + 1 

87 label: Optional[str] = None 

88 if state.src.startswith("(", end): 

89 attrs, next_pos = parse_link(state.src, end + 1, max_nested_level=inline.max_nested_level) 

90 if next_pos is not None: 

91 end = next_pos 

92 if attrs is None: 

93 reference = _parse_reference(state, opener, close, close + 1) 

94 if reference is None: 

95 inline.process_text("]", state) 

96 return close + 1 

97 attrs, label, end = reference 

98 

99 children = state.tokens[opener.token_index + 1 :] 

100 if not opener.image: 

101 # Automatic links inside explicit link text retain their literal text. 

102 children = [ 

103 {"type": "text", "raw": child["_autolink"], "_emphasis": False} if "_autolink" in child else child 

104 for child in children 

105 ] 

106 children = inline.finalize_tokens(children, state.depth + 1) 

107 token: Dict[str, Any] = {"type": "image" if opener.image else "link", "children": children, "attrs": attrs} 

108 if label is not None: 

109 token.update(ref=unikey(label), label=label) 

110 del state.tokens[opener.token_index :] 

111 state.append_token(token) 

112 

113 if not opener.image: 

114 # CommonMark: a successful link deactivates preceding link openers. 

115 # Each opener is deactivated at most once; images remain eligible. 

116 bracket = state.last_link 

117 while bracket is not None and bracket.active: 

118 bracket.active = False 

119 bracket = bracket.previous_link 

120 return end 

121 

122 

123def _parse_reference( 

124 state: InlineState, opener: Bracket, close: int, end: int 

125) -> Optional[Tuple[Dict[str, Any], str, int]]: 

126 refs = state.env.get("ref_links") 

127 if not refs: 

128 return None 

129 

130 label = None 

131 next_pos = None 

132 if state.src.startswith("[", end): 

133 label, next_pos = parse_link_label(state.src, end + 1) 

134 if label is None or label == "": 

135 if opener.nested or close - opener.start > MAX_LINK_LABEL_LENGTH: 

136 return None 

137 label = state.src[opener.start : close] 

138 ref = refs.get(unikey(label)) 

139 if ref is None: 

140 return None 

141 return {"url": ref["url"], "title": ref.get("title")}, label, next_pos or end