def footnote_ref(state: StateInline, silent: bool): """Process footnote references ([^...])""" maximum = state.posMax start = state.pos # should be at least 4 chars - "[^x]" if start + 3 > maximum: return False if "footnotes" not in state.env or "refs" not in state.env["footnotes"]: return False if charCodeAt(state.src, start) != 0x5B: # /* [ */ return False if charCodeAt(state.src, start + 1) != 0x5E: # /* ^ */ return False pos = start + 2 while pos < maximum: if charCodeAt(state.src, pos) == 0x20: return False if charCodeAt(state.src, pos) == 0x0A: return False if charCodeAt(state.src, pos) == 0x5D: # /* ] */ break pos += 1 if pos == start + 2: # no empty footnote labels return False if pos >= maximum: return False pos += 1 label = state.src[start + 2:pos - 1] if (":" + label) not in state.env["footnotes"]["refs"]: return False if not silent: if "list" not in state.env["footnotes"]: state.env["footnotes"]["list"] = {} if state.env["footnotes"]["refs"][":" + label] < 0: footnoteId = len(state.env["footnotes"]["list"]) state.env["footnotes"]["list"][footnoteId] = { "label": label, "count": 0 } state.env["footnotes"]["refs"][":" + label] = footnoteId else: footnoteId = state.env["footnotes"]["refs"][":" + label] footnoteSubId = state.env["footnotes"]["list"][footnoteId]["count"] state.env["footnotes"]["list"][footnoteId]["count"] += 1 token = state.push("footnote_ref", "", 0) token.meta = {"id": footnoteId, "subId": footnoteSubId, "label": label} state.pos = pos state.posMax = maximum return True
def line_comment(state: StateBlock, startLine: int, endLine: int, silent: bool): pos = state.bMarks[startLine] + state.tShift[startLine] maximum = state.eMarks[startLine] # if it's indented more than 3 spaces, it should be a code block if state.sCount[startLine] - state.blkIndent >= 4: return False marker = charCodeAt(state.src, pos) pos += 1 # Check block marker /* % */ if marker != 0x25: return False if silent: return True state.line = startLine + 1 token = state.push("myst_line_comment", "", 0) token.attrSet("class", "myst-line-comment") token.content = state.src[pos:maximum].strip() token.map = [startLine, state.line] token.markup = chr(marker) return True
def dollar_post(string, end): try: nxt = string[end + 1] and charCodeAt(string[end + 1], 0) except IndexError: return True return ((not nxt) or (nxt < 0x30) or (nxt > 0x39)) # no decimal digit .. after closing '$'
def block_break(state: StateBlock, startLine: int, endLine: int, silent: bool): pos = state.bMarks[startLine] + state.tShift[startLine] maximum = state.eMarks[startLine] # if it's indented more than 3 spaces, it should be a code block if state.sCount[startLine] - state.blkIndent >= 4: return False marker = charCodeAt(state.src, pos) pos += 1 # Check block marker /* + */ if marker != 0x2B: return False # markers can be mixed with spaces, but there should be at least 3 of them cnt = 1 while pos < maximum: ch = charCodeAt(state.src, pos) if ch != marker and not isSpace(ch): break if ch == marker: cnt += 1 pos += 1 if cnt < 3: return False if silent: return True state.line = startLine + 1 token = state.push("myst_block_break", "hr", 0) token.attrSet("class", "myst-block") token.content = state.src[pos:maximum].strip() token.map = [startLine, state.line] token.markup = chr(marker) * cnt return True
def footnote_inline(state: StateInline, silent: bool): """Process inline footnotes (^[...])""" maximum = state.posMax start = state.pos if start + 2 >= maximum: return False if charCodeAt(state.src, start) != 0x5E: # /* ^ */ return False if charCodeAt(state.src, start + 1) != 0x5B: # /* [ */ return False labelStart = start + 2 labelEnd = parseLinkLabel(state, start + 1) # parser failed to find ']', so it's not a valid note if labelEnd < 0: return False # We found the end of the link, and know for a fact it's a valid link # so all that's left to do is to call tokenizer. # if not silent: refs = state.env.setdefault("footnotes", {}).setdefault("list", {}) footnoteId = len(refs) tokens = [] state.md.inline.parse(state.src[labelStart:labelEnd], state.md, state.env, tokens) token = state.push("footnote_ref", "", 0) token.meta = {"id": footnoteId} refs[footnoteId] = { "content": state.src[labelStart:labelEnd], "tokens": tokens } state.pos = labelEnd + 1 state.posMax = maximum return True
def myst_role(state: StateInline, silent: bool): try: if charCodeAt(state.src, state.pos - 1) == 0x5C: # /* \ */ # escaped (this could be improved in the case of edge case '\\{') return False except IndexError: pass match = PATTERN.search(state.src[state.pos:]) if not match: return False state.pos += match.end() if not silent: token = state.push("myst_role", "", 0) token.meta = {"name": match.group(1)} token.content = match.group(3) return True
def container_plugin(md: MarkdownIt, name, **options): """Second param may be useful, if you decide to increase minimal allowed marker length """ def validateDefault(params: str, *args): return params.strip().split(" ", 2)[0] == name def renderDefault(self, tokens, idx, _options, env): # add a class to the opening tag if tokens[idx].nesting == 1: tokens[idx].attrJoin("class", name) return self.renderToken(tokens, idx, _options, env) min_markers = 3 marker_str = options.get("marker", ":") marker_char = charCodeAt(marker_str, 0) marker_len = len(marker_str) validate = options.get("validate", validateDefault) render = options.get("render", renderDefault) def container_func(state: StateBlock, startLine: int, endLine: int, silent: bool): auto_closed = False start = state.bMarks[startLine] + state.tShift[startLine] maximum = state.eMarks[startLine] # Check out the first character quickly, # this should filter out most of non-containers if marker_char != charCodeAt(state.src, start): return False # Check out the rest of the marker string pos = start + 1 while pos <= maximum: if marker_str[(pos - start) % marker_len] != state.src[pos]: break pos += 1 marker_count = floor((pos - start) / marker_len) if marker_count < min_markers: return False pos -= (pos - start) % marker_len markup = state.src[start:pos] params = state.src[pos:maximum] if not validate(params, markup): return False # Since start is found, we can report success here in validation mode if silent: return True # Search for the end of the block nextLine = startLine while True: nextLine += 1 if nextLine >= endLine: # unclosed block should be autoclosed by end of document. # also block seems to be autoclosed by end of parent break start = state.bMarks[nextLine] + state.tShift[nextLine] maximum = state.eMarks[nextLine] if start < maximum and state.sCount[nextLine] < state.blkIndent: # non-empty line with negative indent should stop the list: # - ``` # test break if marker_char != charCodeAt(state.src, start): continue if state.sCount[nextLine] - state.blkIndent >= 4: # closing fence should be indented less than 4 spaces continue pos = start + 1 while pos <= maximum: if marker_str[(pos - start) % marker_len] != state.src[pos]: break pos += 1 # closing code fence must be at least as long as the opening one if floor((pos - start) / marker_len) < marker_count: continue # make sure tail has spaces only pos -= (pos - start) % marker_len pos = state.skipSpaces(pos) if pos < maximum: continue # found! auto_closed = True break old_parent = state.parentType old_line_max = state.lineMax state.parentType = "container" # this will prevent lazy continuations from ever going past our end marker state.lineMax = nextLine token = state.push(f"container_{name}_open", "div", 1) token.markup = markup token.block = True token.info = params token.map = [startLine, nextLine] state.md.block.tokenize(state, startLine + 1, nextLine) token = state.push(f"container_{name}_close", "div", -1) token.markup = state.src[start:pos] token.block = True state.parentType = old_parent state.lineMax = old_line_max state.line = nextLine + (1 if auto_closed else 0) return True md.block.ruler.before( "fence", "container_" + name, container_func, {"alt": ["paragraph", "reference", "blockquote", "list"]}, ) md.add_render_rule(f"container_{name}_open", render) md.add_render_rule(f"container_{name}_close", render)
def container_func(state: StateBlock, startLine: int, endLine: int, silent: bool): auto_closed = False start = state.bMarks[startLine] + state.tShift[startLine] maximum = state.eMarks[startLine] # Check out the first character quickly, # this should filter out most of non-containers if marker_char != charCodeAt(state.src, start): return False # Check out the rest of the marker string pos = start + 1 while pos <= maximum: if marker_str[(pos - start) % marker_len] != state.src[pos]: break pos += 1 marker_count = floor((pos - start) / marker_len) if marker_count < min_markers: return False pos -= (pos - start) % marker_len markup = state.src[start:pos] params = state.src[pos:maximum] if not validate(params, markup): return False # Since start is found, we can report success here in validation mode if silent: return True # Search for the end of the block nextLine = startLine while True: nextLine += 1 if nextLine >= endLine: # unclosed block should be autoclosed by end of document. # also block seems to be autoclosed by end of parent break start = state.bMarks[nextLine] + state.tShift[nextLine] maximum = state.eMarks[nextLine] if start < maximum and state.sCount[nextLine] < state.blkIndent: # non-empty line with negative indent should stop the list: # - ``` # test break if marker_char != charCodeAt(state.src, start): continue if state.sCount[nextLine] - state.blkIndent >= 4: # closing fence should be indented less than 4 spaces continue pos = start + 1 while pos <= maximum: if marker_str[(pos - start) % marker_len] != state.src[pos]: break pos += 1 # closing code fence must be at least as long as the opening one if floor((pos - start) / marker_len) < marker_count: continue # make sure tail has spaces only pos -= (pos - start) % marker_len pos = state.skipSpaces(pos) if pos < maximum: continue # found! auto_closed = True break old_parent = state.parentType old_line_max = state.lineMax state.parentType = "container" # this will prevent lazy continuations from ever going past our end marker state.lineMax = nextLine token = state.push(f"container_{name}_open", "div", 1) token.markup = markup token.block = True token.info = params token.map = [startLine, nextLine] state.md.block.tokenize(state, startLine + 1, nextLine) token = state.push(f"container_{name}_close", "div", -1) token.markup = state.src[start:pos] token.block = True state.parentType = old_parent state.lineMax = old_line_max state.line = nextLine + (1 if auto_closed else 0) return True
def make_front_matter_rule(): min_markers = 3 marker_str = "-" marker_char = charCodeAt(marker_str, 0) marker_len = len(marker_str) def frontMatter(state: StateBlock, startLine: int, endLine: int, silent: bool): auto_closed = False start = state.bMarks[startLine] + state.tShift[startLine] maximum = state.eMarks[startLine] # Check out the first character of the first line quickly, # this should filter out non-front matter if startLine != 0 or marker_char != state.srcCharCode[0]: return False # Check out the rest of the marker string # while pos <= 3 pos = start + 1 while pos <= maximum: if marker_str[(pos - start) % marker_len] != state.src[pos]: start_content = pos + 1 break pos += 1 marker_count = floor((pos - start) / marker_len) if marker_count < min_markers: return False pos -= (pos - start) % marker_len # Since start is found, we can report success here in validation mode if silent: return True # Search for the end of the block nextLine = startLine while True: nextLine += 1 if nextLine >= endLine: # unclosed block should be autoclosed by end of document. return False if state.src[start:maximum] == "...": break start = state.bMarks[nextLine] + state.tShift[nextLine] maximum = state.eMarks[nextLine] if start < maximum and state.sCount[nextLine] < state.blkIndent: # non-empty line with negative indent should stop the list: # - ``` # test break if marker_char != state.srcCharCode[start]: continue if state.sCount[nextLine] - state.blkIndent >= 4: # closing fence should be indented less than 4 spaces continue pos = start + 1 while pos < maximum: if marker_str[(pos - start) % marker_len] != state.src[pos]: break pos += 1 # closing code fence must be at least as long as the opening one if floor((pos - start) / marker_len) < marker_count: continue # make sure tail has spaces only pos -= (pos - start) % marker_len pos = state.skipSpaces(pos) if pos < maximum: continue # found! auto_closed = True break old_parent = state.parentType old_line_max = state.lineMax state.parentType = "container" # this will prevent lazy continuations from ever going past our end marker state.lineMax = nextLine token = state.push("front_matter", "", 0) token.hidden = True token.markup = marker_str * min_markers token.content = state.src[state.bMarks[startLine + 1]:state.eMarks[nextLine - 1]] token.block = True token.meta = state.src[start_content:start - 1] state.parentType = old_parent state.lineMax = old_line_max state.line = nextLine + (1 if auto_closed else 0) token.map = [startLine, state.line] return True return frontMatter
def container_plugin( md: MarkdownIt, name: str, marker: str = ":", validate: Optional[Callable[[str, str], bool]] = None, render=None, ): """Plugin ported from `markdown-it-container <https://github.com/markdown-it/markdown-it-container>`__. It is a plugin for creating block-level custom containers: .. code-block:: md :::: name ::: name *markdown* ::: :::: :param name: the name of the container to parse :param marker: the marker character to use :param validate: func(marker, param) -> bool, default matches against the name :param render: render func """ def validateDefault(params: str, *args): return params.strip().split(" ", 2)[0] == name def renderDefault(self, tokens, idx, _options, env): # add a class to the opening tag if tokens[idx].nesting == 1: tokens[idx].attrJoin("class", name) return self.renderToken(tokens, idx, _options, env) min_markers = 3 marker_str = marker marker_char = charCodeAt(marker_str, 0) marker_len = len(marker_str) validate = validate or validateDefault render = render or renderDefault def container_func(state: StateBlock, startLine: int, endLine: int, silent: bool): auto_closed = False start = state.bMarks[startLine] + state.tShift[startLine] maximum = state.eMarks[startLine] # Check out the first character quickly, # this should filter out most of non-containers if marker_char != state.srcCharCode[start]: return False # Check out the rest of the marker string pos = start + 1 while pos <= maximum: try: character = state.src[pos] except IndexError: break if marker_str[(pos - start) % marker_len] != character: break pos += 1 marker_count = floor((pos - start) / marker_len) if marker_count < min_markers: return False pos -= (pos - start) % marker_len markup = state.src[start:pos] params = state.src[pos:maximum] assert validate is not None if not validate(params, markup): return False # Since start is found, we can report success here in validation mode if silent: return True # Search for the end of the block nextLine = startLine while True: nextLine += 1 if nextLine >= endLine: # unclosed block should be autoclosed by end of document. # also block seems to be autoclosed by end of parent break start = state.bMarks[nextLine] + state.tShift[nextLine] maximum = state.eMarks[nextLine] if start < maximum and state.sCount[nextLine] < state.blkIndent: # non-empty line with negative indent should stop the list: # - ``` # test break if marker_char != state.srcCharCode[start]: continue if state.sCount[nextLine] - state.blkIndent >= 4: # closing fence should be indented less than 4 spaces continue pos = start + 1 while pos <= maximum: try: character = state.src[pos] except IndexError: break if marker_str[(pos - start) % marker_len] != character: break pos += 1 # closing code fence must be at least as long as the opening one if floor((pos - start) / marker_len) < marker_count: continue # make sure tail has spaces only pos -= (pos - start) % marker_len pos = state.skipSpaces(pos) if pos < maximum: continue # found! auto_closed = True break old_parent = state.parentType old_line_max = state.lineMax state.parentType = "container" # this will prevent lazy continuations from ever going past our end marker state.lineMax = nextLine token = state.push(f"container_{name}_open", "div", 1) token.markup = markup token.block = True token.info = params token.map = [startLine, nextLine] state.md.block.tokenize(state, startLine + 1, nextLine) token = state.push(f"container_{name}_close", "div", -1) token.markup = state.src[start:pos] token.block = True state.parentType = old_parent state.lineMax = old_line_max state.line = nextLine + (1 if auto_closed else 0) return True md.block.ruler.before( "fence", "container_" + name, container_func, {"alt": ["paragraph", "reference", "blockquote", "list"]}, ) md.add_render_rule(f"container_{name}_open", render) md.add_render_rule(f"container_{name}_close", render)
def dollar_pre(str, beg): prv = charCodeAt(str[beg - 1], 0) if beg > 0 else False return ( (not prv) or prv != 0x5C and (prv < 0x30 or prv > 0x39) # no backslash, ) # no decimal digit .. before opening '$'
def footnote_def(state: StateBlock, startLine: int, endLine: int, silent: bool): """Process footnote block definition""" start = state.bMarks[startLine] + state.tShift[startLine] maximum = state.eMarks[startLine] # line should be at least 5 chars - "[^x]:" if start + 4 > maximum: return False if charCodeAt(state.src, start) != 0x5B: # /* [ */ return False if charCodeAt(state.src, start + 1) != 0x5E: # /* ^ */ return False pos = start + 2 while pos < maximum: if charCodeAt(state.src, pos) == 0x20: return False if charCodeAt(state.src, pos) == 0x5D: # /* ] */ break pos += 1 if pos == start + 2: # no empty footnote labels return False pos += 1 if pos + 1 >= maximum or charCodeAt(state.src, pos) != 0x3A: # /* : */ return False if silent: return True pos += 1 label = state.src[start + 2:pos - 2] state.env.setdefault("footnotes", {}).setdefault("refs", {})[":" + label] = -1 open_token = Token("footnote_reference_open", "", 1) open_token.meta = {"label": label} open_token.level = state.level state.level += 1 state.tokens.append(open_token) oldBMark = state.bMarks[startLine] oldTShift = state.tShift[startLine] oldSCount = state.sCount[startLine] oldParentType = state.parentType posAfterColon = pos initial = offset = (state.sCount[startLine] + pos - (state.bMarks[startLine] + state.tShift[startLine])) while pos < maximum: ch = charCodeAt(state.src, pos) if isSpace(ch): if ch == 0x09: offset += 4 - offset % 4 else: offset += 1 else: break pos += 1 state.tShift[startLine] = pos - posAfterColon state.sCount[startLine] = offset - initial state.bMarks[startLine] = posAfterColon state.blkIndent += 4 state.parentType = "footnote" if state.sCount[startLine] < state.blkIndent: state.sCount[startLine] += state.blkIndent state.md.block.tokenize(state, startLine, endLine, True) state.parentType = oldParentType state.blkIndent -= 4 state.tShift[startLine] = oldTShift state.sCount[startLine] = oldSCount state.bMarks[startLine] = oldBMark open_token.map = [startLine, state.line] token = Token("footnote_reference_close", "", -1) state.level -= 1 token.level = state.level state.tokens.append(token) return True