Coverage for src/midgy/front_matter.py: 90%

1 from enum import Enum

2 from re import compile

3

4 __all__ = ("load",)

5 SHEBANG = compile("^#!(?P<interpreter>\S+)\s+(?P<command>\S*)")

6

7

8 FM = Enum("FM", {"yaml": "-", "toml": "+"})

9

10

11 def strip_and_classifiy(x):

12 x = x.strip()

13 return FM(x[0]), "".join(x.splitlines(True)[1:-1])

14

15

16 def load(x):

17 """load front matter including the delimiters.

18

19 --- and +++ reference yaml and toml front matter respectively."""

20 kind, x = strip_and_classifiy(x)

21 return {FM.toml: load_toml, FM.yaml: load_yaml}[kind](x)

22

23

24 def load_toml(x):

25 from tomli import loads

26

27 return loads(x)

28

29

30 def load_yaml(x):

31 return _get_yaml_loader()(x)

32

33

34 def _get_yaml_loader():

35 loader = getattr(_get_yaml_loader, "loader", None)

36 if loader:

37 return loader

38 try:

39 from ruamel.yaml import safe_load as load

40 except ModuleNotFoundError:

41 try:

42 from yaml import safe_load as load

43 except ModuleNotFoundError:

44 from json import loads as load

45 _get_yaml_loader.loader = load

46 return load

47

48

49 def _shebang_lexer(state, startLine, endLine, silent):

50 start = state.bMarks[startLine] + state.tShift[startLine]

51 maximum = state.eMarks[startLine]

52

53 # our front matter allows for indents and can occur at positions

54 # other than 0

55 # this should filter out non-front matter

56 if state.tokens:

57 return False

58

59 m = SHEBANG.match(state.src[start: maximum])

60 if not m:

61 return False

62

63 parent = state.parentType

64 line_max = state.lineMax

65

66 # this will prevent lazy continuations from ever going past our end marker

67 state.lineMax = startLine

68

69 token = state.push("shebang", "", 0)

70 token.hidden = True

71 token.content = state.getLines(startLine, startLine + 1, 0, True)

72 token.block = True

73

74 state.parentType = parent

75 state.lineMax = line_max

76 state.line = startLine + 1

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

78

79 return True

80

81

82 def _front_matter_lexer(state, startLine, endLine, silent):

83 auto_closed = False

84 start = state.bMarks[startLine] + state.tShift[startLine]

85 maximum = state.eMarks[startLine]

86 src_len = len(state.src)

87 if state.tokens:

88 if len(state.tokens) == 1:

89 if state.tokens[0].type != "shebang":

90 return False

91

92 # our front matter allows for indents and can occur at positions

93 # other than 0

94 # this should filter out non-front matter

95

96 if state.sCount[startLine]:

97 return False

98

99 if state.tokens:

100 if len(state.tokens) > 1:

101 return False

102 if state.tokens[-1].type != "shebang":

103 return False

104

105 markup = None

106 if state.srcCharCode[start] == ord("-"):

107 markup = "-"

108 elif state.srcCharCode[start] == ord("+"):

109 markup = "+"

110 else:

111 return False

112

113 if state.srcCharCode[start + 1 : maximum] != tuple(map(ord, (markup, markup))):

114 return False

115

116 # Search for the end of the block

117 nextLine = startLine

118

119 while True:

120 nextLine += 1

121 if nextLine >= endLine:

122 return False

123

124 start = state.bMarks[nextLine] + state.tShift[nextLine]

125 maximum = state.eMarks[nextLine]

126

127 if start < maximum and state.sCount[nextLine] < state.blkIndent:

128 break

129

130 if ord(markup) != state.srcCharCode[start]:

131 continue

132

133 if state.sCount[nextLine] - state.blkIndent >= 4:

134 continue

135

136 if state.srcCharCode[start + 1 : maximum] == tuple(map(ord, (markup, markup))):

137 auto_closed = True

138 nextLine += 1

139 break

140

141 parent = state.parentType

142 line_max = state.lineMax

143 state.parentType = "container"

144

145 # this will prevent lazy continuations from ever going past our end marker

146 state.lineMax = nextLine

147

148 token = state.push("front_matter", "", 0)

149 token.hidden = True

150 token.markup = markup

151 token.content = state.getLines(startLine, nextLine, 0, True)

152 token.block = True

153

154 state.parentType = parent

155 state.lineMax = line_max

156 state.line = nextLine

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

158

159 return True