Coverage for C:\leo.repo\leo-editor\leo\plugins\importers\leo_rst.py: 90%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

113 statements  

1#@+leo-ver=5-thin 

2#@+node:ekr.20140723122936.18151: * @file ../plugins/importers/leo_rst.py 

3""" 

4The @auto importer for restructured text. 

5 

6This module must **not** be named rst, so as not to conflict with docutils. 

7""" 

8from typing import Dict 

9from leo.core import leoGlobals as g 

10from leo.plugins.importers import linescanner 

11Importer = linescanner.Importer 

12# Used by writers.leo_rst as well as in this file. 

13underlines = '*=-^~"\'+!$%&(),./:;<>?@[\\]_`{|}#' 

14 # All valid rst underlines, with '#' *last*, so it is effectively reserved. 

15#@+others 

16#@+node:ekr.20161127192007.2: ** class Rst_Importer 

17class Rst_Importer(Importer): 

18 """The importer for the rst lanuage.""" 

19 

20 def __init__(self, importCommands, **kwargs): 

21 """Rst_Importer.__init__""" 

22 super().__init__(importCommands, 

23 language='rest', 

24 state_class=Rst_ScanState, 

25 strict=False, 

26 ) 

27 

28 #@+others 

29 #@+node:ekr.20161204032455.1: *3* rst_i.check 

30 def check(self, unused_s, parent): 

31 """ 

32 Suppress perfect-import checks for rST. 

33 

34 There is no reason to retain specic underlinings, nor is there any 

35 reason to prevent the writer from inserting conditional newlines. 

36 """ 

37 return True 

38 #@+node:ekr.20161129040921.2: *3* rst_i.gen_lines & helpers 

39 def gen_lines(self, lines, parent): 

40 """Node generator for reStructuredText importer.""" 

41 if all(s.isspace() for s in lines): 

42 return 

43 self.vnode_info = { 

44 # Keys are vnodes, values are inner dicts. 

45 parent.v: { 

46 'lines': [], 

47 } 

48 } 

49 # We may as well do this first. See note below. 

50 self.stack = [parent] 

51 skip = 0 

52 

53 for i, line in enumerate(lines): 

54 if skip > 0: 

55 skip -= 1 

56 elif self.is_lookahead_overline(i, lines): 

57 level = self.ch_level(line[0]) 

58 self.make_node(level, lines[i + 1]) 

59 skip = 2 

60 elif self.is_lookahead_underline(i, lines): 

61 level = self.ch_level(lines[i + 1][0]) 

62 self.make_node(level, line) 

63 skip = 1 

64 elif i == 0: 

65 p = self.make_dummy_node('!Dummy chapter') 

66 self.add_line(p, line) 

67 else: 

68 p = self.stack[-1] 

69 self.add_line(p, line) 

70 #@+node:ekr.20161129040921.5: *4* rst_i.find_parent 

71 def find_parent(self, level, h): 

72 """ 

73 Return the parent at the indicated level, allocating 

74 place-holder nodes as necessary. 

75 """ 

76 assert level > 0 

77 while level < len(self.stack): 

78 self.stack.pop() 

79 # Insert placeholders as necessary. 

80 # This could happen in imported files not created by us. 

81 while level > len(self.stack): 

82 top = self.stack[-1] 

83 child = self.create_child_node( 

84 parent=top, 

85 line=None, 

86 headline='placeholder', 

87 ) 

88 self.stack.append(child) 

89 # Create the desired node. 

90 top = self.stack[-1] 

91 child = self.create_child_node( 

92 parent=top, 

93 line=None, 

94 headline=h, # Leave the headline alone 

95 ) 

96 self.stack.append(child) 

97 return self.stack[level] 

98 #@+node:ekr.20161129111503.1: *4* rst_i.is_lookahead_overline 

99 def is_lookahead_overline(self, i, lines): 

100 """True if lines[i:i+2] form an overlined/underlined line.""" 

101 if i + 2 < len(lines): 

102 line0 = lines[i] 

103 line1 = lines[i + 1] 

104 line2 = lines[i + 2] 

105 ch0 = self.is_underline(line0, extra='#') 

106 ch1 = self.is_underline(line1) 

107 ch2 = self.is_underline(line2, extra='#') 

108 return ( 

109 ch0 and ch2 and ch0 == ch2 and 

110 not ch1 and 

111 len(line1) >= 4 and 

112 len(line0) >= len(line1) and 

113 len(line2) >= len(line1) 

114 ) 

115 return False 

116 #@+node:ekr.20161129112703.1: *4* rst_i.is_lookahead_underline 

117 def is_lookahead_underline(self, i, lines): 

118 """True if lines[i:i+1] form an underlined line.""" 

119 if i + 1 < len(lines): 

120 line0 = lines[i] 

121 line1 = lines[i + 1] 

122 ch0 = self.is_underline(line0) 

123 ch1 = self.is_underline(line1) 

124 return not line0.isspace() and not ch0 and ch1 and 4 <= len(line1) 

125 return False 

126 #@+node:ekr.20161129040921.8: *4* rst_i.is_underline 

127 def is_underline(self, line, extra=None): 

128 """True if the line consists of nothing but the same underlining characters.""" 

129 if line.isspace(): 

130 return None 

131 chars = underlines 

132 if extra: 

133 chars = chars + extra 

134 ch1 = line[0] 

135 if ch1 not in chars: 

136 return None 

137 for ch in line.rstrip(): 

138 if ch != ch1: 

139 return None 

140 return ch1 

141 

142 #@+node:ekr.20161129040921.6: *4* rst_i.make_dummy_node 

143 def make_dummy_node(self, headline): 

144 """Make a decls node.""" 

145 parent = self.stack[-1] 

146 assert parent == self.root, repr(parent) 

147 child = self.create_child_node( 

148 parent=self.stack[-1], 

149 line=None, 

150 headline=headline, 

151 ) 

152 self.stack.append(child) 

153 return child 

154 #@+node:ekr.20161129040921.7: *4* rst_i.make_node 

155 def make_node(self, level, headline): 

156 """Create a new node, with the given headline.""" 

157 self.find_parent(level=level, h=headline) 

158 #@+node:ekr.20161129045020.1: *4* rst_i.ch_level 

159 # # 430, per RagBlufThim. Was {'#': 1,} 

160 rst_seen: Dict[str, int] = {} 

161 rst_level = 0 # A trick. 

162 

163 def ch_level(self, ch): 

164 """Return the underlining level associated with ch.""" 

165 assert ch in underlines, (repr(ch), g.callers()) 

166 d = self.rst_seen 

167 if ch in d: 

168 return d.get(ch) 

169 self.rst_level += 1 

170 d[ch] = self.rst_level 

171 return self.rst_level 

172 #@+node:ekr.20161129040921.11: *3* rst_i.post_pass 

173 def post_pass(self, parent): 

174 """A do-nothing post-pass for markdown.""" 

175 #@-others 

176#@+node:ekr.20161127192007.6: ** class Rst_ScanState 

177class Rst_ScanState: 

178 """A class representing the state of the rst line-oriented scan.""" 

179 

180 def __init__(self, d=None): 

181 """Rst_ScanState.__init__""" 

182 if d: 

183 prev = d.get('prev') 

184 self.context = prev.context 

185 else: 

186 self.context = '' 

187 

188 def __repr__(self): 

189 """Rst_ScanState.__repr__""" 

190 return "Rst_ScanState context: %r " % (self.context) 

191 

192 __str__ = __repr__ 

193 

194 #@+others 

195 #@+node:ekr.20161127192007.7: *3* rst_state.level 

196 def level(self): 

197 """Rst_ScanState.level.""" 

198 return 0 

199 

200 #@+node:ekr.20161127192007.8: *3* rst_state.update 

201 def update(self, data): 

202 """ 

203 Rst_ScanState.update 

204 

205 Update the state using the 6-tuple returned by i.scan_line. 

206 Return i = data[1] 

207 """ 

208 context, i, delta_c, delta_p, delta_s, bs_nl = data 

209 # All ScanState classes must have a context ivar. 

210 self.context = context 

211 return i 

212 #@-others 

213#@-others 

214def do_import(c, s, parent): 

215 return Rst_Importer(c.importCommands).run(s, parent) 

216importer_dict = { 

217 '@auto': ['@auto-rst',], # Fix #392: @auto-rst file.txt: -rst ignored on read 

218 'func': Rst_Importer.do_import(), 

219 'extensions': ['.rst', '.rest'], 

220} 

221#@@language python 

222#@@tabwidth -4 

223#@-leo