Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/mdit_py_plugins/tasklists/__init__.py: 46%

69 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-06-07 06:15 +0000

1"""Builds task/todo lists out of markdown lists with items starting with [ ] or [x]""" 

2 

3# Ported by Wolmar Nyberg Åkerström from https://github.com/revin/markdown-it-task-lists 

4# ISC License 

5# Copyright (c) 2016, Revin Guillen 

6# 

7# Permission to use, copy, modify, and/or distribute this software for any 

8# purpose with or without fee is hereby granted, provided that the above 

9# copyright notice and this permission notice appear in all copies. 

10# 

11# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 

12# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 

13# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 

14# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 

15# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 

16# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 

17# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 

18from __future__ import annotations 

19 

20import re 

21from uuid import uuid4 

22 

23from markdown_it import MarkdownIt 

24from markdown_it.rules_core import StateCore 

25from markdown_it.token import Token 

26 

27# Regex string to match a whitespace character, as specified in 

28# https://github.github.com/gfm/#whitespace-character 

29# (spec version 0.29-gfm (2019-04-06)) 

30_GFM_WHITESPACE_RE = r"[ \t\n\v\f\r]" 

31 

32 

33def tasklists_plugin( 

34 md: MarkdownIt, 

35 enabled: bool = False, 

36 label: bool = False, 

37 label_after: bool = False, 

38) -> None: 

39 """Plugin for building task/todo lists out of markdown lists with items starting with [ ] or [x] 

40 .. Nothing else 

41 

42 For example:: 

43 - [ ] An item that needs doing 

44 - [x] An item that is complete 

45 

46 The rendered HTML checkboxes are disabled; to change this, pass a truthy value into the enabled 

47 property of the plugin options. 

48 

49 :param enabled: True enables the rendered checkboxes 

50 :param label: True wraps the rendered list items in a <label> element for UX purposes, 

51 :param label_after: True adds the <label> element after the checkbox. 

52 """ 

53 disable_checkboxes = not enabled 

54 use_label_wrapper = label 

55 use_label_after = label_after 

56 

57 def fcn(state: StateCore) -> None: 

58 tokens = state.tokens 

59 for i in range(2, len(tokens) - 1): 

60 if is_todo_item(tokens, i): 

61 todoify(tokens[i]) 

62 tokens[i - 2].attrSet( 

63 "class", 

64 "task-list-item" + (" enabled" if not disable_checkboxes else ""), 

65 ) 

66 tokens[parent_token(tokens, i - 2)].attrSet( 

67 "class", "contains-task-list" 

68 ) 

69 

70 md.core.ruler.after("inline", "github-tasklists", fcn) 

71 

72 def parent_token(tokens: list[Token], index: int) -> int: 

73 target_level = tokens[index].level - 1 

74 for i in range(1, index + 1): 

75 if tokens[index - i].level == target_level: 

76 return index - i 

77 return -1 

78 

79 def is_todo_item(tokens: list[Token], index: int) -> bool: 

80 return ( 

81 is_inline(tokens[index]) 

82 and is_paragraph(tokens[index - 1]) 

83 and is_list_item(tokens[index - 2]) 

84 and starts_with_todo_markdown(tokens[index]) 

85 ) 

86 

87 def todoify(token: Token) -> None: 

88 assert token.children is not None 

89 token.children.insert(0, make_checkbox(token)) 

90 token.children[1].content = token.children[1].content[3:] 

91 token.content = token.content[3:] 

92 

93 if use_label_wrapper: 

94 if use_label_after: 

95 token.children.pop() 

96 

97 # Replaced number generator from original plugin with uuid. 

98 checklist_id = f"task-item-{uuid4()}" 

99 token.children[0].content = ( 

100 token.children[0].content[0:-1] + f' id="{checklist_id}">' 

101 ) 

102 token.children.append(after_label(token.content, checklist_id)) 

103 else: 

104 token.children.insert(0, begin_label()) 

105 token.children.append(end_label()) 

106 

107 def make_checkbox(token: Token) -> Token: 

108 checkbox = Token("html_inline", "", 0) 

109 disabled_attr = 'disabled="disabled"' if disable_checkboxes else "" 

110 if token.content.startswith("[ ] "): 

111 checkbox.content = ( 

112 '<input class="task-list-item-checkbox" ' 

113 f'{disabled_attr} type="checkbox">' 

114 ) 

115 elif token.content.startswith("[x] ") or token.content.startswith("[X] "): 

116 checkbox.content = ( 

117 '<input class="task-list-item-checkbox" checked="checked" ' 

118 f'{disabled_attr} type="checkbox">' 

119 ) 

120 return checkbox 

121 

122 def begin_label() -> Token: 

123 token = Token("html_inline", "", 0) 

124 token.content = "<label>" 

125 return token 

126 

127 def end_label() -> Token: 

128 token = Token("html_inline", "", 0) 

129 token.content = "</label>" 

130 return token 

131 

132 def after_label(content: str, checkbox_id: str) -> Token: 

133 token = Token("html_inline", "", 0) 

134 token.content = ( 

135 f'<label class="task-list-item-label" for="{checkbox_id}">{content}</label>' 

136 ) 

137 token.attrs = {"for": checkbox_id} 

138 return token 

139 

140 def is_inline(token: Token) -> bool: 

141 return token.type == "inline" 

142 

143 def is_paragraph(token: Token) -> bool: 

144 return token.type == "paragraph_open" 

145 

146 def is_list_item(token: Token) -> bool: 

147 return token.type == "list_item_open" 

148 

149 def starts_with_todo_markdown(token: Token) -> bool: 

150 # leading whitespace in a list item is already trimmed off by markdown-it 

151 return re.match(rf"\[[ xX]]{_GFM_WHITESPACE_RE}+", token.content) is not None