Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/mdit_py_plugins/anchors/index.py: 83%

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

41 statements  

1import re 

2from typing import Callable, List, Optional, Set 

3 

4from markdown_it import MarkdownIt 

5from markdown_it.rules_core import StateCore 

6from markdown_it.token import Token 

7 

8 

9def anchors_plugin( 

10 md: MarkdownIt, 

11 min_level: int = 1, 

12 max_level: int = 2, 

13 slug_func: Optional[Callable[[str], str]] = None, 

14 permalink: bool = False, 

15 permalinkSymbol: str = "¶", 

16 permalinkBefore: bool = False, 

17 permalinkSpace: bool = True, 

18) -> None: 

19 """Plugin for adding header anchors, based on 

20 `markdown-it-anchor <https://github.com/valeriangalliat/markdown-it-anchor>`__ 

21 

22 .. code-block:: md 

23 

24 # Title String 

25 

26 renders as: 

27 

28 .. code-block:: html 

29 

30 <h1 id="title-string">Title String <a class="header-anchor" href="#title-string">¶</a></h1> 

31 

32 :param min_level: minimum header level to apply anchors 

33 :param max_level: maximum header level to apply anchors 

34 :param slug_func: function to convert title text to id slug. 

35 :param permalink: Add a permalink next to the title 

36 :param permalinkSymbol: the symbol to show 

37 :param permalinkBefore: Add the permalink before the title, otherwise after 

38 :param permalinkSpace: Add a space between the permalink and the title 

39 

40 Note, the default slug function aims to mimic the GitHub Markdown format, see: 

41 

42 - https://github.com/jch/html-pipeline/blob/master/lib/html/pipeline/toc_filter.rb 

43 - https://gist.github.com/asabaylus/3071099 

44 

45 """ 

46 selected_levels = list(range(min_level, max_level + 1)) 

47 md.core.ruler.push( 

48 "anchor", 

49 _make_anchors_func( 

50 selected_levels, 

51 slug_func or slugify, 

52 permalink, 

53 permalinkSymbol, 

54 permalinkBefore, 

55 permalinkSpace, 

56 ), 

57 ) 

58 

59 

60def _make_anchors_func( 

61 selected_levels: List[int], 

62 slug_func: Callable[[str], str], 

63 permalink: bool, 

64 permalinkSymbol: str, 

65 permalinkBefore: bool, 

66 permalinkSpace: bool, 

67) -> Callable[[StateCore], None]: 

68 def _anchor_func(state: StateCore) -> None: 

69 slugs: Set[str] = set() 

70 for idx, token in enumerate(state.tokens): 

71 if token.type != "heading_open": 

72 continue 

73 level = int(token.tag[1]) 

74 if level not in selected_levels: 

75 continue 

76 inline_token = state.tokens[idx + 1] 

77 assert inline_token.children is not None 

78 title = "".join( 

79 child.content 

80 for child in inline_token.children 

81 if child.type in ["text", "code_inline"] 

82 ) 

83 slug = unique_slug(slug_func(title), slugs) 

84 token.attrSet("id", slug) 

85 

86 if permalink: 

87 link_open = Token( 

88 "link_open", 

89 "a", 

90 1, 

91 ) 

92 link_open.attrSet("class", "header-anchor") 

93 link_open.attrSet("href", f"#{slug}") 

94 link_tokens = [ 

95 link_open, 

96 Token("html_block", "", 0, content=permalinkSymbol), 

97 Token("link_close", "a", -1), 

98 ] 

99 if permalinkBefore: 

100 inline_token.children = ( 

101 link_tokens 

102 + ( 

103 [Token("text", "", 0, content=" ")] 

104 if permalinkSpace 

105 else [] 

106 ) 

107 + inline_token.children 

108 ) 

109 else: 

110 inline_token.children.extend( 

111 ([Token("text", "", 0, content=" ")] if permalinkSpace else []) 

112 + link_tokens 

113 ) 

114 

115 return _anchor_func 

116 

117 

118def slugify(title: str) -> str: 

119 return re.sub(r"[^\w\u4e00-\u9fff\- ]", "", title.strip().lower().replace(" ", "-")) 

120 

121 

122def unique_slug(slug: str, slugs: Set[str]) -> str: 

123 uniq = slug 

124 i = 1 

125 while uniq in slugs: 

126 uniq = f"{slug}-{i}" 

127 i += 1 

128 slugs.add(uniq) 

129 return uniq