1"""Process definition lists.""" 
    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 deflist_plugin(md: MarkdownIt) -> None: 
    10    """Plugin ported from 
    11    `markdown-it-deflist <https://github.com/markdown-it/markdown-it-deflist>`__. 
    12 
    13    The syntax is based on 
    14    `pandoc definition lists <http://johnmacfarlane.net/pandoc/README.html#definition-lists>`__: 
    15 
    16    .. code-block:: md 
    17 
    18        Term 1 
    19        : Definition 1 long form 
    20 
    21          second paragraph 
    22 
    23        Term 2 with *inline markup* 
    24        ~ Definition 2a compact style 
    25        ~ Definition 2b 
    26 
    27    """ 
    28 
    29    def skipMarker(state: StateBlock, line: int) -> int: 
    30        """Search `[:~][\n ]`, returns next pos after marker on success or -1 on fail.""" 
    31        start = state.bMarks[line] + state.tShift[line] 
    32        maximum = state.eMarks[line] 
    33 
    34        if start >= maximum: 
    35            return -1 
    36 
    37        # Check bullet 
    38        marker = state.src[start] 
    39        start += 1 
    40        if marker != "~" and marker != ":": 
    41            return -1 
    42 
    43        pos = state.skipSpaces(start) 
    44 
    45        # require space after ":" 
    46        if start == pos: 
    47            return -1 
    48 
    49        # no empty definitions, e.g. "  : " 
    50        if pos >= maximum: 
    51            return -1 
    52 
    53        return start 
    54 
    55    def markTightParagraphs(state: StateBlock, idx: int) -> None: 
    56        level = state.level + 2 
    57 
    58        i = idx + 2 
    59        l2 = len(state.tokens) - 2 
    60        while i < l2: 
    61            if ( 
    62                state.tokens[i].level == level 
    63                and state.tokens[i].type == "paragraph_open" 
    64            ): 
    65                state.tokens[i + 2].hidden = True 
    66                state.tokens[i].hidden = True 
    67                i += 2 
    68            i += 1 
    69 
    70    def deflist(state: StateBlock, startLine: int, endLine: int, silent: bool) -> bool: 
    71        if is_code_block(state, startLine): 
    72            return False 
    73 
    74        if silent: 
    75            # quirk: validation mode validates a dd block only, not a whole deflist 
    76            if state.ddIndent < 0: 
    77                return False 
    78            return skipMarker(state, startLine) >= 0 
    79 
    80        nextLine = startLine + 1 
    81        if nextLine >= endLine: 
    82            return False 
    83 
    84        if state.isEmpty(nextLine): 
    85            nextLine += 1 
    86            if nextLine >= endLine: 
    87                return False 
    88 
    89        if state.sCount[nextLine] < state.blkIndent: 
    90            return False 
    91        contentStart = skipMarker(state, nextLine) 
    92        if contentStart < 0: 
    93            return False 
    94 
    95        # Start list 
    96        listTokIdx = len(state.tokens) 
    97        tight = True 
    98 
    99        token = state.push("dl_open", "dl", 1) 
    100        token.map = listLines = [startLine, 0] 
    101 
    102        # Iterate list items 
    103        dtLine = startLine 
    104        ddLine = nextLine 
    105 
    106        # One definition list can contain multiple DTs, 
    107        # and one DT can be followed by multiple DDs. 
    108        # 
    109        # Thus, there is two loops here, and label is 
    110        # needed to break out of the second one 
    111        # 
    112        break_outer = False 
    113 
    114        while True: 
    115            prevEmptyEnd = False 
    116 
    117            token = state.push("dt_open", "dt", 1) 
    118            token.map = [dtLine, dtLine] 
    119 
    120            token = state.push("inline", "", 0) 
    121            token.map = [dtLine, dtLine] 
    122            token.content = state.getLines( 
    123                dtLine, dtLine + 1, state.blkIndent, False 
    124            ).strip() 
    125            token.children = [] 
    126 
    127            token = state.push("dt_close", "dt", -1) 
    128 
    129            while True: 
    130                token = state.push("dd_open", "dd", 1) 
    131                token.map = itemLines = [nextLine, 0] 
    132 
    133                pos = contentStart 
    134                maximum = state.eMarks[ddLine] 
    135                offset = ( 
    136                    state.sCount[ddLine] 
    137                    + contentStart 
    138                    - (state.bMarks[ddLine] + state.tShift[ddLine]) 
    139                ) 
    140 
    141                while pos < maximum: 
    142                    if state.src[pos] == "\t": 
    143                        offset += 4 - offset % 4 
    144                    elif state.src[pos] == " ": 
    145                        offset += 1 
    146                    else: 
    147                        break 
    148 
    149                    pos += 1 
    150 
    151                contentStart = pos 
    152 
    153                oldTight = state.tight 
    154                oldDDIndent = state.ddIndent 
    155                oldIndent = state.blkIndent 
    156                oldTShift = state.tShift[ddLine] 
    157                oldSCount = state.sCount[ddLine] 
    158                oldParentType = state.parentType 
    159                state.blkIndent = state.ddIndent = state.sCount[ddLine] + 2 
    160                state.tShift[ddLine] = contentStart - state.bMarks[ddLine] 
    161                state.sCount[ddLine] = offset 
    162                state.tight = True 
    163                state.parentType = "deflist" 
    164 
    165                state.md.block.tokenize(state, ddLine, endLine) 
    166 
    167                # If any of list item is tight, mark list as tight 
    168                if not state.tight or prevEmptyEnd: 
    169                    tight = False 
    170 
    171                # Item become loose if finish with empty line, 
    172                # but we should filter last element, because it means list finish 
    173                prevEmptyEnd = (state.line - ddLine) > 1 and state.isEmpty( 
    174                    state.line - 1 
    175                ) 
    176 
    177                state.tShift[ddLine] = oldTShift 
    178                state.sCount[ddLine] = oldSCount 
    179                state.tight = oldTight 
    180                state.parentType = oldParentType 
    181                state.blkIndent = oldIndent 
    182                state.ddIndent = oldDDIndent 
    183 
    184                token = state.push("dd_close", "dd", -1) 
    185 
    186                itemLines[1] = nextLine = state.line 
    187 
    188                if nextLine >= endLine: 
    189                    break_outer = True 
    190                    break 
    191 
    192                if state.sCount[nextLine] < state.blkIndent: 
    193                    break_outer = True 
    194                    break 
    195 
    196                contentStart = skipMarker(state, nextLine) 
    197                if contentStart < 0: 
    198                    break 
    199 
    200                ddLine = nextLine 
    201 
    202                # go to the next loop iteration: 
    203                # insert DD tag and repeat checking 
    204 
    205            if break_outer: 
    206                break_outer = False 
    207                break 
    208 
    209            if nextLine >= endLine: 
    210                break 
    211            dtLine = nextLine 
    212 
    213            if state.isEmpty(dtLine): 
    214                break 
    215            if state.sCount[dtLine] < state.blkIndent: 
    216                break 
    217 
    218            ddLine = dtLine + 1 
    219            if ddLine >= endLine: 
    220                break 
    221            if state.isEmpty(ddLine): 
    222                ddLine += 1 
    223            if ddLine >= endLine: 
    224                break 
    225 
    226            if state.sCount[ddLine] < state.blkIndent: 
    227                break 
    228            contentStart = skipMarker(state, ddLine) 
    229            if contentStart < 0: 
    230                break 
    231 
    232            # go to the next loop iteration: 
    233            # insert DT and DD tags and repeat checking 
    234 
    235        # Finalise list 
    236        token = state.push("dl_close", "dl", -1) 
    237 
    238        listLines[1] = nextLine 
    239 
    240        state.line = nextLine 
    241 
    242        # mark paragraphs tight if needed 
    243        if tight: 
    244            markTightParagraphs(state, listTokIdx) 
    245 
    246        return True 
    247 
    248    md.block.ruler.before( 
    249        "paragraph", 
    250        "deflist", 
    251        deflist, 
    252        {"alt": ["paragraph", "reference", "blockquote"]}, 
    253    )