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

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1#@+leo-ver=5-thin
2#@+node:ekr.20140723122936.18151: * @file ../plugins/importers/leo_rst.py
3"""
4The @auto importer for restructured text.
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."""
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 )
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.
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
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
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.
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."""
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 = ''
188 def __repr__(self):
189 """Rst_ScanState.__repr__"""
190 return "Rst_ScanState context: %r " % (self.context)
192 __str__ = __repr__
194 #@+others
195 #@+node:ekr.20161127192007.7: *3* rst_state.level
196 def level(self):
197 """Rst_ScanState.level."""
198 return 0
200 #@+node:ekr.20161127192007.8: *3* rst_state.update
201 def update(self, data):
202 """
203 Rst_ScanState.update
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