Example #1
0
def target(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

    text = state.src[pos:maximum].strip()
    if not text.startswith("("):
        return False
    if not text.endswith(")="):
        return False
    if not text[1:-2]:
        return False

    if silent:
        return True

    state.line = startLine + 1

    token = state.push("myst_target", "", 0)
    token.attrSet("class", "myst-target")
    token.content = text[1:-2]
    token.map = [startLine, state.line]

    return True
Example #2
0
def amsmath_block(state: StateBlock, startLine: int, endLine: int,
                  silent: bool):

    # if it's indented more than 3 spaces, it should be a code block
    if state.sCount[startLine] - state.blkIndent >= 4:
        return False

    begin = state.bMarks[startLine] + state.tShift[startLine]

    outcome = match_environment(state.src[begin:])
    if not outcome:
        return False
    environment, numbered, endpos = outcome
    endpos += begin

    line = startLine
    while line < endLine:
        if endpos >= state.bMarks[line] and endpos <= state.eMarks[line]:
            # line for end of block math found ...
            state.line = line + 1
            break
        line += 1

    if not silent:
        token = state.push("amsmath", "math", 0)
        token.block = True
        token.content = state.src[begin:endpos]
        token.meta = {"environment": environment, "numbered": numbered}
        token.map = [startLine, line]

    return True
Example #3
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 = state.srcCharCode[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 #4
0
    def skipMarker(state: StateBlock, line: int):
        """Search `[:~][\n ]`, returns next pos after marker on success or -1 on fail."""
        start = state.bMarks[line] + state.tShift[line]
        maximum = state.eMarks[line]

        if start >= maximum:
            return -1

        # Check bullet
        marker = state.srcCharCode[start]
        start += 1
        if marker != 0x7E and marker != 0x3A:  # ~ :
            return -1

        pos = state.skipSpaces(start)

        # require space after ":"
        if start == pos:
            return -1

        # no empty definitions, e.g. "  : "
        if pos >= maximum:
            return -1

        return start
Example #5
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 = state.srcCharCode[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 = state.srcCharCode[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 #6
0
    def _substitution_block(state: StateBlock, startLine: int, endLine: int,
                            silent: bool):
        startPos = state.bMarks[startLine] + state.tShift[startLine]
        end = 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

        lineText = state.src[startPos:end].strip()

        try:
            if (lineText[0] != start_delimiter
                    or lineText[1] != start_delimiter
                    or lineText[-1] != end_delimiter
                    or lineText[-2] != end_delimiter or len(lineText) < 5):
                return False
        except IndexError:
            return False

        text = lineText[2:-2].strip()

        # special case if multiple on same line, e.g. {{a}}{{b}}
        if (end_delimiter * 2) in text:
            return False

        state.line = startLine + 1

        if silent:
            return True

        token = state.push("substitution_block", "div", 0)
        token.block = True
        token.content = text
        token.attrSet("class", "substitution")
        token.attrSet("text", text)
        token.markup = f"{start_delimiter}{end_delimiter}"
        token.map = [startLine, state.line]

        return True
Example #7
0
def target(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

    match = TARGET_PATTERN.match(state.src[pos:maximum])
    if not match:
        return False

    if silent:
        return True

    state.line = startLine + 1

    token = state.push("myst_target", "", 0)
    token.attrSet("class", "myst-target")
    token.content = match.group(1)
    token.map = [startLine, state.line]

    return True
Example #8
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

    if state.src[pos] != "%":
        return False

    if silent:
        return True

    token = state.push("myst_line_comment", "", 0)
    token.attrSet("class", "myst-line-comment")
    token.content = state.src[pos + 1:maximum].rstrip()
    token.markup = "%"

    # search end of block while appending lines to `token.content`
    for nextLine in itertools.count(startLine + 1):
        if nextLine >= endLine:
            break
        pos = state.bMarks[nextLine] + state.tShift[nextLine]
        maximum = state.eMarks[nextLine]

        if state.src[pos] != "%":
            break
        token.content += "\n" + state.src[pos + 1:maximum].rstrip()

    state.line = nextLine
    token.map = [startLine, nextLine]

    return True
Example #9
0
    def front_matter(state: StateBlock, start_line: int, end_line: int,
                     silent: bool):
        # grab initial data if it's : separated
        if start_line != 0:
            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
        next_line = start_line
        start_content = 0
        meta = {}

        while True:
            next_line += 1
            if next_line >= end_line:
                # unclosed block should be autoclosed by end of document.
                return False

            start = state.bMarks[next_line]
            maximum = state.eMarks[next_line]

            if start == maximum:
                # empty line is terminator
                break

            key_value = state.src[start:maximum].split(":", 1)
            if len(key_value) != 2:
                # Error here, we have no k/v separator
                return False
            meta[key_value[0].lower()] = key_value[1]
        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 = next_line

        token = state.push("pelican_frontmatter", "", 0)
        # token.hidden = True
        token.content = state.src[state.bMarks[start_content]:state.
                                  eMarks[next_line]]
        token.block = True
        token.meta = meta

        state.parentType = old_parent
        state.lineMax = old_line_max
        state.line = next_line
        token.map = [start_line, state.line]

        # consider taking the content into a dictionary and checking for Title: .+\n

        return True
def test_block_func():
    block_func = main.make_block_func(main.rules.dollars.block[0])
    md = MarkdownIt()
    src = r"$$\na=1\n\nc\nb=2$$ (abc)"
    tokens = []
    state = StateBlock(src, md, {}, tokens)
    block_func(state, 0, 10, False)
    assert tokens[0].as_dict() == {
        "type": "math_block_eqno",
        "tag": "math",
        "nesting": 0,
        "attrs": None,
        "map": None,
        "level": 0,
        "children": None,
        "content": "\\na=1\\n\\nc\\nb=2",
        "markup": "$$",
        "info": "abc",
        "meta": {},
        "block": True,
        "hidden": False,
    }
Example #11
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 #12
0
    def _math_block_dollar(state: StateBlock, startLine: int, endLine: int,
                           silent: bool) -> bool:

        # TODO internal backslash escaping

        haveEndMarker = False
        startPos = state.bMarks[startLine] + state.tShift[startLine]
        end = 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

        if startPos + 2 > end:
            return False

        if (state.srcCharCode[startPos] != 0x24
                or state.srcCharCode[startPos + 1] != 0x24):  # /* $ */
            return False

        # search for end of block
        nextLine = startLine
        label = None

        # search for end of block on same line
        lineText = state.src[startPos:end]
        if len(lineText.strip()) > 3:

            if lineText.strip().endswith("$$"):
                haveEndMarker = True
                end = end - 2 - (len(lineText) - len(lineText.strip()))
            elif allow_labels:
                # reverse the line and match
                eqnoMatch = DOLLAR_EQNO_REV.match(lineText[::-1])
                if eqnoMatch:
                    haveEndMarker = True
                    label = eqnoMatch.group(1)[::-1]
                    end = end - eqnoMatch.end()

        # search for end of block on subsequent line
        if not haveEndMarker:
            while True:
                nextLine += 1
                if nextLine >= endLine:
                    break

                start = state.bMarks[nextLine] + state.tShift[nextLine]
                end = state.eMarks[nextLine]

                if end - start < 2:
                    continue

                lineText = state.src[start:end]

                if lineText.strip().endswith("$$"):
                    haveEndMarker = True
                    end = end - 2 - (len(lineText) - len(lineText.strip()))
                    break

                # reverse the line and match
                if allow_labels:
                    eqnoMatch = DOLLAR_EQNO_REV.match(lineText[::-1])
                    if eqnoMatch:
                        haveEndMarker = True
                        label = eqnoMatch.group(1)[::-1]
                        end = end - eqnoMatch.end()
                        break

        if not haveEndMarker:
            return False

        state.line = nextLine + (1 if haveEndMarker else 0)

        token = state.push("math_block_label" if label else "math_block",
                           "math", 0)
        token.block = True
        token.content = state.src[startPos + 2:end]
        token.markup = "$$"
        token.map = [startLine, state.line]
        if label:
            token.info = label if label_normalizer is None else label_normalizer(
                label)

        return True
Example #13
0
def test_footnote_tail():
    md = MarkdownIt()

    tokens = [
        Token(
            **{
                "type": "footnote_reference_open",
                "tag": "",
                "nesting": 1,
                "attrs": None,
                "map": None,
                "level": 0,
                "children": None,
                "content": "",
                "markup": "",
                "info": "",
                "meta": {
                    "label": "a"
                },
                "block": False,
                "hidden": False,
            }),
        Token(
            **{
                "type": "paragraph_open",
                "tag": "p",
                "nesting": 1,
                "attrs": None,
                "map": [0, 1],
                "level": 1,
                "children": None,
                "content": "",
                "markup": "",
                "info": "",
                "meta": {},
                "block": True,
                "hidden": False,
            }),
        Token(
            **{
                "type": "inline",
                "tag": "",
                "nesting": 0,
                "attrs": None,
                "map": [0, 1],
                "level": 2,
                "children": [],
                "content": "xyz",
                "markup": "",
                "info": "",
                "meta": {},
                "block": True,
                "hidden": False,
            }),
        Token(
            **{
                "type": "paragraph_close",
                "tag": "p",
                "nesting": -1,
                "attrs": None,
                "map": None,
                "level": 1,
                "children": None,
                "content": "",
                "markup": "",
                "info": "",
                "meta": {},
                "block": True,
                "hidden": False,
            }),
        Token(
            **{
                "type": "footnote_reference_close",
                "tag": "",
                "nesting": -1,
                "attrs": None,
                "map": None,
                "level": 0,
                "children": None,
                "content": "",
                "markup": "",
                "info": "",
                "meta": {},
                "block": False,
                "hidden": False,
            }),
        Token("other", "", 0),
    ]
    env = {
        "footnotes": {
            "refs": {
                ":a": 0
            },
            "list": {
                0: {
                    "label": "a",
                    "count": 1
                }
            }
        }
    }
    state = StateBlock("", md, env, tokens)
    index.footnote_tail(state)
    assert state.tokens == [
        Token(
            type="other",
            tag="",
            nesting=0,
            attrs=None,
            map=None,
            level=0,
            children=None,
            content="",
            markup="",
            info="",
            meta={},
            block=False,
            hidden=False,
        ),
        Token(
            type="footnote_block_open",
            tag="",
            nesting=1,
            attrs=None,
            map=None,
            level=0,
            children=None,
            content="",
            markup="",
            info="",
            meta={},
            block=False,
            hidden=False,
        ),
        Token(
            type="footnote_open",
            tag="",
            nesting=1,
            attrs=None,
            map=None,
            level=0,
            children=None,
            content="",
            markup="",
            info="",
            meta={
                "id": 0,
                "label": "a"
            },
            block=False,
            hidden=False,
        ),
        Token(
            type="paragraph_open",
            tag="p",
            nesting=1,
            attrs=None,
            map=[0, 1],
            level=1,
            children=None,
            content="",
            markup="",
            info="",
            meta={},
            block=True,
            hidden=False,
        ),
        Token(
            type="inline",
            tag="",
            nesting=0,
            attrs=None,
            map=[0, 1],
            level=2,
            children=[],
            content="xyz",
            markup="",
            info="",
            meta={},
            block=True,
            hidden=False,
        ),
        Token(
            type="footnote_anchor",
            tag="",
            nesting=0,
            attrs=None,
            map=None,
            level=0,
            children=None,
            content="",
            markup="",
            info="",
            meta={
                "id": 0,
                "subId": 0,
                "label": "a"
            },
            block=False,
            hidden=False,
        ),
        Token(
            type="paragraph_close",
            tag="p",
            nesting=-1,
            attrs=None,
            map=None,
            level=1,
            children=None,
            content="",
            markup="",
            info="",
            meta={},
            block=True,
            hidden=False,
        ),
        Token(
            type="footnote_close",
            tag="",
            nesting=-1,
            attrs=None,
            map=None,
            level=0,
            children=None,
            content="",
            markup="",
            info="",
            meta={},
            block=False,
            hidden=False,
        ),
        Token(
            type="footnote_block_close",
            tag="",
            nesting=-1,
            attrs=None,
            map=None,
            level=0,
            children=None,
            content="",
            markup="",
            info="",
            meta={},
            block=False,
            hidden=False,
        ),
    ]
Example #14
0
def _rule(state: StateBlock, startLine: int, endLine: int, silent: bool):

    haveEndMarker = False
    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

    if pos + 3 > maximum:
        return False

    marker = state.srcCharCode[pos]

    # /* : */
    if marker != 0x3A:
        return False

    # scan marker length
    mem = pos
    pos = state.skipChars(pos, marker)

    length = pos - mem

    if length < 3:
        return False

    markup = state.src[mem:pos]
    params = state.src[pos:maximum]

    # Since start is found, we can report success here in validation mode
    if silent:
        return True

    # search end of 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

        pos = mem = state.bMarks[nextLine] + state.tShift[nextLine]
        maximum = state.eMarks[nextLine]

        if pos < maximum and state.sCount[nextLine] < state.blkIndent:
            # non-empty line with negative indent should stop the list:
            # - ```
            #  test
            break

        if state.srcCharCode[pos] != marker:
            continue

        if state.sCount[nextLine] - state.blkIndent >= 4:
            # closing fence should be indented less than 4 spaces
            continue

        pos = state.skipChars(pos, marker)

        # closing code fence must be at least as long as the opening one
        if pos - mem < length:
            continue

        # make sure tail has spaces only
        pos = state.skipSpaces(pos)

        if pos < maximum:
            continue

        haveEndMarker = True
        # found!
        break

    # If a fence has heading spaces, they should be removed from its inner block
    length = state.sCount[startLine]

    state.line = nextLine + (1 if haveEndMarker else 0)

    token = state.push("colon_fence", "code", 0)
    token.info = stripEscape(params)
    token.content = state.getLines(startLine + 1, nextLine, length, True)
    token.markup = markup
    token.map = [startLine, state.line]

    return True
Example #15
0
def footnote_tail(state: StateBlock, *args, **kwargs):
    """Post-processing step, to move footnote tokens to end of the token stream.

    Also removes un-referenced tokens.
    """

    insideRef = False
    refTokens = {}

    if "footnotes" not in state.env:
        return

    current = []
    tok_filter = []
    for tok in state.tokens:

        if tok.type == "footnote_reference_open":
            insideRef = True
            current = []
            currentLabel = tok.meta["label"]
            tok_filter.append(False)
            continue

        if tok.type == "footnote_reference_close":
            insideRef = False
            # prepend ':' to avoid conflict with Object.prototype members
            refTokens[":" + currentLabel] = current
            tok_filter.append(False)
            continue

        if insideRef:
            current.append(tok)

        tok_filter.append((not insideRef))

    state.tokens = [t for t, f in zip(state.tokens, tok_filter) if f]

    if "list" not in state.env.get("footnotes", {}):
        return
    foot_list = state.env["footnotes"]["list"]

    token = Token("footnote_block_open", "", 1)
    state.tokens.append(token)

    for i, foot_note in foot_list.items():
        token = Token("footnote_open", "", 1)
        token.meta = {"id": i, "label": foot_note.get("label", None)}
        # TODO propagate line positions of original foot note
        # (but don't store in token.map, because this is used for scroll syncing)
        state.tokens.append(token)

        if "tokens" in foot_note:
            tokens = []

            token = Token("paragraph_open", "p", 1)
            token.block = True
            tokens.append(token)

            token = Token("inline", "", 0)
            token.children = foot_note["tokens"]
            token.content = foot_note["content"]
            tokens.append(token)

            token = Token("paragraph_close", "p", -1)
            token.block = True
            tokens.append(token)

        elif "label" in foot_note:
            tokens = refTokens[":" + foot_note["label"]]

        state.tokens.extend(tokens)
        if state.tokens[len(state.tokens) - 1].type == "paragraph_close":
            lastParagraph = state.tokens.pop()
        else:
            lastParagraph = None

        t = (foot_note["count"] if
             (("count" in foot_note) and (foot_note["count"] > 0)) else 1)
        j = 0
        while j < t:
            token = Token("footnote_anchor", "", 0)
            token.meta = {
                "id": i,
                "subId": j,
                "label": foot_note.get("label", None)
            }
            state.tokens.append(token)
            j += 1

        if lastParagraph:
            state.tokens.append(lastParagraph)

        token = Token("footnote_close", "", -1)
        state.tokens.append(token)

    token = Token("footnote_block_close", "", -1)
    state.tokens.append(token)
Example #16
0
def test_footnote_def():

    md = MarkdownIt()
    src = r"[^a]: xyz"
    tokens = []
    state = StateBlock(src, md, {}, tokens)
    index.footnote_def(state, 0, 1, False)
    assert [t.as_dict() for t in tokens] == [
        {
            "type": "footnote_reference_open",
            "tag": "",
            "nesting": 1,
            "attrs": None,
            "map": [0, 1],
            "level": 0,
            "children": None,
            "content": "",
            "markup": "",
            "info": "",
            "meta": {
                "label": "a"
            },
            "block": False,
            "hidden": False,
        },
        {
            "type": "paragraph_open",
            "tag": "p",
            "nesting": 1,
            "attrs": None,
            "map": [0, 1],
            "level": 1,
            "children": None,
            "content": "",
            "markup": "",
            "info": "",
            "meta": {},
            "block": True,
            "hidden": False,
        },
        {
            "type": "inline",
            "tag": "",
            "nesting": 0,
            "attrs": None,
            "map": [0, 1],
            "level": 2,
            "children": [],
            "content": "xyz",
            "markup": "",
            "info": "",
            "meta": {},
            "block": True,
            "hidden": False,
        },
        {
            "type": "paragraph_close",
            "tag": "p",
            "nesting": -1,
            "attrs": None,
            "map": None,
            "level": 1,
            "children": None,
            "content": "",
            "markup": "",
            "info": "",
            "meta": {},
            "block": True,
            "hidden": False,
        },
        {
            "type": "footnote_reference_close",
            "tag": "",
            "nesting": -1,
            "attrs": None,
            "map": None,
            "level": 0,
            "children": None,
            "content": "",
            "markup": "",
            "info": "",
            "meta": {},
            "block": False,
            "hidden": False,
        },
    ]
    assert state.env == {"footnotes": {"refs": {":a": -1}}}
Example #17
0
    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
Example #18
0
    def deflist(state: StateBlock, startLine: int, endLine: int, silent: bool):

        if silent:
            # quirk: validation mode validates a dd block only, not a whole deflist
            if state.ddIndent < 0:
                return False
            return skipMarker(state, startLine) >= 0

        nextLine = startLine + 1
        if nextLine >= endLine:
            return False

        if state.isEmpty(nextLine):
            nextLine += 1
            if nextLine >= endLine:
                return False

        if state.sCount[nextLine] < state.blkIndent:
            return False
        contentStart = skipMarker(state, nextLine)
        if contentStart < 0:
            return False

        # Start list
        listTokIdx = len(state.tokens)
        tight = True

        token = state.push("dl_open", "dl", 1)
        token.map = listLines = [startLine, 0]

        # Iterate list items
        dtLine = startLine
        ddLine = nextLine

        # One definition list can contain multiple DTs,
        # and one DT can be followed by multiple DDs.
        #
        # Thus, there is two loops here, and label is
        # needed to break out of the second one
        #
        break_outer = False

        while True:
            prevEmptyEnd = False

            token = state.push("dt_open", "dt", 1)
            token.map = [dtLine, dtLine]

            token = state.push("inline", "", 0)
            token.map = [dtLine, dtLine]
            token.content = state.getLines(dtLine, dtLine + 1, state.blkIndent,
                                           False).strip()
            token.children = []

            token = state.push("dt_close", "dt", -1)

            while True:
                token = state.push("dd_open", "dd", 1)
                token.map = itemLines = [nextLine, 0]

                pos = contentStart
                maximum = state.eMarks[ddLine]
                offset = (state.sCount[ddLine] + contentStart -
                          (state.bMarks[ddLine] + state.tShift[ddLine]))

                while pos < maximum:
                    ch = state.srcCharCode[pos]

                    if isSpace(ch):
                        if ch == 0x09:
                            offset += 4 - offset % 4
                        else:
                            offset += 1
                    else:
                        break

                    pos += 1

                contentStart = pos

                oldTight = state.tight
                oldDDIndent = state.ddIndent
                oldIndent = state.blkIndent
                oldTShift = state.tShift[ddLine]
                oldSCount = state.sCount[ddLine]
                oldParentType = state.parentType
                state.blkIndent = state.ddIndent = state.sCount[ddLine] + 2
                state.tShift[ddLine] = contentStart - state.bMarks[ddLine]
                state.sCount[ddLine] = offset
                state.tight = True
                state.parentType = "deflist"

                state.md.block.tokenize(state, ddLine, endLine, True)

                # If any of list item is tight, mark list as tight
                if not state.tight or prevEmptyEnd:
                    tight = False

                # Item become loose if finish with empty line,
                # but we should filter last element, because it means list finish
                prevEmptyEnd = (state.line -
                                ddLine) > 1 and state.isEmpty(state.line - 1)

                state.tShift[ddLine] = oldTShift
                state.sCount[ddLine] = oldSCount
                state.tight = oldTight
                state.parentType = oldParentType
                state.blkIndent = oldIndent
                state.ddIndent = oldDDIndent

                token = state.push("dd_close", "dd", -1)

                itemLines[1] = nextLine = state.line

                if nextLine >= endLine:
                    break_outer = True
                    break

                if state.sCount[nextLine] < state.blkIndent:
                    break_outer = True
                    break

                contentStart = skipMarker(state, nextLine)
                if contentStart < 0:
                    break

                ddLine = nextLine

                # go to the next loop iteration:
                # insert DD tag and repeat checking

            if break_outer:
                break_outer = False
                break

            if nextLine >= endLine:
                break
            dtLine = nextLine

            if state.isEmpty(dtLine):
                break
            if state.sCount[dtLine] < state.blkIndent:
                break

            ddLine = dtLine + 1
            if ddLine >= endLine:
                break
            if state.isEmpty(ddLine):
                ddLine += 1
            if ddLine >= endLine:
                break

            if state.sCount[ddLine] < state.blkIndent:
                break
            contentStart = skipMarker(state, ddLine)
            if contentStart < 0:
                break

            # go to the next loop iteration:
            # insert DT and DD tags and repeat checking

        # Finalise list
        token = state.push("dl_close", "dl", -1)

        listLines[1] = nextLine

        state.line = nextLine

        # mark paragraphs tight if needed
        if tight:
            markTightParagraphs(state, listTokIdx)

        return True
Example #19
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 state.srcCharCode[start] != 0x5B:  # /* [ */
        return False
    if state.srcCharCode[start + 1] != 0x5E:  # /* ^ */
        return False

    pos = start + 2
    while pos < maximum:
        if state.srcCharCode[pos] == 0x20:
            return False
        if state.srcCharCode[pos] == 0x5D:  # /* ] */
            break
        pos += 1

    if pos == start + 2:  # no empty footnote labels
        return False
    pos += 1
    if pos + 1 >= maximum or state.srcCharCode[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 = state.srcCharCode[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
Example #20
0
def set_parent_type(state: StateBlock, name: str):
    """Temporarily set parent type to `name`"""
    oldParentType = state.parentType
    state.parentType = name
    yield
    state.parentType = oldParentType
Example #21
0
def _fieldlist_rule(state: StateBlock, startLine: int, endLine: int, silent: bool):
    # adapted from markdown_it/rules_block/list.py::list_block

    # if it's indented more than 3 spaces, it should be a code block
    if state.sCount[startLine] - state.blkIndent >= 4:
        return False

    posAfterName, name_text = parseNameMarker(state, startLine)
    if posAfterName < 0:
        return False

    # For validation mode we can terminate immediately
    if silent:
        return True

    # start field list
    token = state.push("field_list_open", "dl", 1)
    token.attrSet("class", "field-list")
    token.map = listLines = [startLine, 0]

    # iterate list items
    nextLine = startLine

    with set_parent_type(state, "fieldlist"):

        while nextLine < endLine:

            # create name tokens
            token = state.push("fieldlist_name_open", "dt", 1)
            token.map = [startLine, startLine]
            token = state.push("inline", "", 0)
            token.map = [startLine, startLine]
            token.content = name_text
            token.children = []
            token = state.push("fieldlist_name_close", "dt", -1)

            # set indent positions
            pos = posAfterName
            maximum = state.eMarks[nextLine]
            offset = (
                state.sCount[nextLine]
                + posAfterName
                - (state.bMarks[startLine] + state.tShift[startLine])
            )

            # find indent to start of body on first line
            while pos < maximum:
                ch = state.srcCharCode[pos]

                if ch == 0x09:  # \t
                    offset += 4 - (offset + state.bsCount[nextLine]) % 4
                elif ch == 0x20:  # \s
                    offset += 1
                else:
                    break

                pos += 1

            contentStart = pos

            # set indent for body text
            if contentStart >= maximum:
                # no body on first line, so use constant indentation
                # TODO adapt to indentation of subsequent lines?
                indent = 2
            else:
                indent = offset

            # Run subparser on the field body
            token = state.push("fieldlist_body_open", "dd", 1)
            token.map = itemLines = [startLine, 0]

            # change current state, then restore it after parser subcall
            oldTShift = state.tShift[startLine]
            oldSCount = state.sCount[startLine]
            oldBlkIndent = state.blkIndent

            state.tShift[startLine] = contentStart - state.bMarks[startLine]
            state.sCount[startLine] = offset
            state.blkIndent = indent

            state.md.block.tokenize(state, startLine, endLine)

            state.blkIndent = oldBlkIndent
            state.tShift[startLine] = oldTShift
            state.sCount[startLine] = oldSCount

            token = state.push("fieldlist_body_close", "dd", -1)

            nextLine = startLine = state.line
            itemLines[1] = nextLine

            if nextLine >= endLine:
                break

            contentStart = state.bMarks[startLine]

            # Try to check if list is terminated or continued.
            if state.sCount[nextLine] < state.blkIndent:
                break

            # if it's indented more than 3 spaces, it should be a code block
            if state.sCount[startLine] - state.blkIndent >= 4:
                break

            # get next field item
            posAfterName, name_text = parseNameMarker(state, startLine)
            if posAfterName < 0:
                break

        # Finalize list
        token = state.push("field_list_close", "dl", -1)
        listLines[1] = nextLine
        state.line = nextLine

    return True