1"""Process front matter."""
2
3from markdown_it import MarkdownIt
4from markdown_it.rules_block import StateBlock
5
6from mdit_py_plugins.utils import is_code_block
7
8
9def front_matter_plugin(md: MarkdownIt) -> None:
10 """Plugin ported from
11 `markdown-it-front-matter <https://github.com/ParkSB/markdown-it-front-matter>`__.
12
13 It parses initial metadata, stored between opening/closing dashes:
14
15 .. code-block:: md
16
17 ---
18 valid-front-matter: true
19 ---
20
21 """
22 md.block.ruler.before(
23 "table",
24 "front_matter",
25 _front_matter_rule,
26 {"alt": ["paragraph", "reference", "blockquote", "list"]},
27 )
28
29
30def _front_matter_rule(
31 state: StateBlock, startLine: int, endLine: int, silent: bool
32) -> bool:
33 marker_chr = "-"
34 min_markers = 3
35
36 auto_closed = False
37 start = state.bMarks[startLine] + state.tShift[startLine]
38 maximum = state.eMarks[startLine]
39 src_len = len(state.src)
40
41 # Check out the first character of the first line quickly,
42 # this should filter out non-front matter
43 if startLine != 0 or state.src[0] != marker_chr:
44 return False
45
46 # Check out the rest of the marker string
47 # while pos <= 3
48 pos = start + 1
49 while pos <= maximum and pos < src_len:
50 if state.src[pos] != marker_chr:
51 break
52 pos += 1
53
54 marker_count = pos - start
55
56 if marker_count < min_markers:
57 return False
58
59 # Since start is found, we can report success here in validation mode
60 if silent:
61 return True
62
63 # Search for the end of the block
64 nextLine = startLine
65
66 while True:
67 nextLine += 1
68 if nextLine >= endLine:
69 # unclosed block should be autoclosed by end of document.
70 return False
71
72 if state.src[start:maximum] == "...":
73 break
74
75 start = state.bMarks[nextLine] + state.tShift[nextLine]
76 maximum = state.eMarks[nextLine]
77
78 if start < maximum and state.sCount[nextLine] < state.blkIndent:
79 # non-empty line with negative indent should stop the list:
80 # - ```
81 # test
82 break
83
84 if state.src[start] != marker_chr:
85 continue
86
87 if is_code_block(state, nextLine):
88 continue
89
90 pos = start + 1
91 while pos < maximum:
92 if state.src[pos] != marker_chr:
93 break
94 pos += 1
95
96 # closing code fence must be at least as long as the opening one
97 if (pos - start) < marker_count:
98 continue
99
100 # make sure tail has spaces only
101 pos = state.skipSpaces(pos)
102
103 if pos < maximum:
104 continue
105
106 # found!
107 auto_closed = True
108 break
109
110 old_parent = state.parentType
111 old_line_max = state.lineMax
112 state.parentType = "container"
113
114 # this will prevent lazy continuations from ever going past our end marker
115 state.lineMax = nextLine
116
117 token = state.push("front_matter", "", 0)
118 token.hidden = True
119 token.markup = marker_chr * min_markers
120 token.content = state.src[state.bMarks[startLine + 1] : state.eMarks[nextLine - 1]]
121 token.block = True
122
123 state.parentType = old_parent
124 state.lineMax = old_line_max
125 state.line = nextLine + (1 if auto_closed else 0)
126 token.map = [startLine, state.line]
127
128 return True