Example #1
0
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
Example #2
0
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
Example #3
0
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 '$'
Example #4
0
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
Example #5
0
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
Example #6
0
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
Example #7
0
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)
Example #8
0
    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
Example #9
0
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
Example #10
0
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)
Example #11
0
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 '$'
Example #12
0
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