Mercurial > jouvence
annotate fontaine/parser.py @ 1:74b83e3d921e
Add more states, add more tests.
author | Ludovic Chabant <ludovic@chabant.com> |
---|---|
date | Mon, 02 Jan 2017 21:54:59 -0800 |
parents | 243401c49520 |
children | 59fe8cb6190d |
rev | line source |
---|---|
0 | 1 import re |
2 import logging | |
3 | |
4 | |
5 logger = logging.getLogger(__name__) | |
6 | |
7 | |
8 class FontaineState: | |
9 can_merge = False | |
1
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
10 needs_pending_empty_lines = True |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
11 |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
12 def __init__(self): |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
13 self.has_pending_empty_line = False |
0 | 14 |
15 def match(self, fp, ctx): | |
16 return False | |
17 | |
18 def consume(self, fp, ctx): | |
19 raise NotImplementedError() | |
20 | |
1
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
21 def merge(self): |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
22 pass |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
23 |
0 | 24 def exit(self, ctx): |
25 pass | |
26 | |
27 | |
1
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
28 class _PassThroughState(FontaineState): |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
29 def consume(self, fp, ctx): |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
30 return ANY_STATE |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
31 |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
32 |
0 | 33 class FontaineParserError(Exception): |
34 def __init__(self, line_no, message): | |
35 super().__init__("Error line %d: %s" % (line_no, message)) | |
36 | |
37 | |
38 ANY_STATE = object() | |
39 EOF_STATE = object() | |
40 | |
41 | |
42 RE_EMPTY_LINE = re.compile(r"^$", re.M) | |
43 RE_BLANK_LINE = re.compile(r"^\s*$", re.M) | |
44 | |
45 RE_TITLE_KEY_VALUE = re.compile(r"^(?P<key>[\w\s\-]+)\s*:") | |
46 | |
47 | |
48 class _TitlePageState(FontaineState): | |
49 def __init__(self): | |
50 super().__init__() | |
51 self._cur_key = None | |
52 self._cur_val = None | |
53 | |
1
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
54 def match(self, fp, ctx): |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
55 line = fp.peekline() |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
56 return RE_TITLE_KEY_VALUE.match(line) |
0 | 57 |
1
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
58 def consume(self, fp, ctx): |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
59 while True: |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
60 line = fp.readline() |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
61 if not line: |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
62 return EOF_STATE |
0 | 63 |
1
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
64 m = RE_TITLE_KEY_VALUE.match(line) |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
65 if m: |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
66 # Commit current value, start new one. |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
67 self._commit(ctx) |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
68 self._cur_key = m.group('key') |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
69 self._cur_val = line[m.end():].strip() |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
70 else: |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
71 # Keep accumulating the value of one of the title page's |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
72 # values. |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
73 self._cur_val += line.strip() |
0 | 74 |
1
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
75 if RE_EMPTY_LINE.match(fp.peekline()): |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
76 self._commit(ctx) |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
77 # Finished with the page title, now move on to the first scene. |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
78 self.has_pending_empty_line = True |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
79 break |
0 | 80 |
1
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
81 return ANY_STATE |
0 | 82 |
83 def exit(self, ctx): | |
84 self._commit(ctx) | |
85 | |
86 def _commit(self, ctx): | |
87 if self._cur_key is not None: | |
88 ctx.document.title_values[self._cur_key] = self._cur_val | |
89 self._cur_key = None | |
90 self._cur_val = None | |
91 | |
92 | |
93 RE_SCENE_HEADER_PATTERN = re.compile( | |
94 r"^(int|ext|est|int/ext|int./ext|i/e)[\s\.]", re.I) | |
95 | |
96 | |
97 class _SceneHeaderState(FontaineState): | |
98 def match(self, fp, ctx): | |
1
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
99 lines = fp.peeklines(3) |
0 | 100 return ( |
101 RE_EMPTY_LINE.match(lines[0]) and | |
1
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
102 RE_SCENE_HEADER_PATTERN.match(lines[1]) and |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
103 RE_EMPTY_LINE.match(lines[2])) |
0 | 104 |
105 def consume(self, fp, ctx): | |
106 fp.readline() # Get past the blank line. | |
107 line = fp.readline().rstrip('\r\n') | |
108 line = line.lstrip('.') # In case it was forced. | |
109 ctx.document.addScene(line) | |
1
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
110 self.has_pending_empty_line = True |
0 | 111 return ANY_STATE |
112 | |
113 | |
114 class _ActionState(FontaineState): | |
115 can_merge = True | |
1
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
116 needs_pending_empty_lines = False |
0 | 117 |
118 def __init__(self): | |
119 super().__init__() | |
120 self.text = '' | |
1
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
121 self._to_merge = None |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
122 self._was_merged = False |
0 | 123 |
124 def match(self, fp, ctx): | |
125 return True | |
126 | |
127 def consume(self, fp, ctx): | |
128 is_first_line = True | |
129 while True: | |
130 line = fp.readline() | |
131 if not line: | |
132 return EOF_STATE | |
133 | |
134 if is_first_line: | |
1
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
135 line = line.lstrip('!') # In case it was forced. |
0 | 136 is_first_line = False |
137 | |
1
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
138 # If the next line is empty, strip the carriage return from |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
139 # the line we just got because it's probably gonna be the |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
140 # last one. |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
141 if RE_EMPTY_LINE.match(fp.peekline()): |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
142 stripped_line = line.rstrip("\r\n") |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
143 self.text += stripped_line |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
144 self._to_merge = line[len(stripped_line):] |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
145 break |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
146 # ...otherwise, add the line with in full. |
0 | 147 self.text += line |
148 | |
1
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
149 return ANY_STATE |
0 | 150 |
1
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
151 def merge(self): |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
152 # Put back the stuff we stripped from what we thought was the |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
153 # last line. |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
154 self.text += self._to_merge |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
155 self._was_merged = True |
0 | 156 |
157 def exit(self, ctx): | |
158 ctx.document.lastScene().addAction(self.text) | |
159 | |
160 | |
1
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
161 RE_CENTERED_LINE = re.compile(r"^\s*>\s*.*\s*<\s*$", re.M) |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
162 |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
163 |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
164 class _CenteredActionState(FontaineState): |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
165 def __init__(self): |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
166 super().__init__() |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
167 self.text = '' |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
168 self._aborted = False |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
169 |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
170 def match(self, fp, ctx): |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
171 lines = fp.peeklines(2) |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
172 return ( |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
173 RE_EMPTY_LINE.match(lines[0]) and |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
174 RE_CENTERED_LINE.match(lines[1])) |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
175 |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
176 def consume(self, fp, ctx): |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
177 snapshot = fp.snapshot() |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
178 fp.readline() # Get past the empty line. |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
179 while True: |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
180 line = fp.readline() |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
181 if not line: |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
182 return EOF_STATE |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
183 |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
184 clean_line = line.rstrip('\r\n') |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
185 eol = line[len(clean_line):] |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
186 |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
187 clean_line = clean_line.strip() |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
188 if clean_line[0] != '>' or clean_line[-1] != '<': |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
189 # The whole paragraph must have `>` and `<` wrappers, so |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
190 # if we detect a line that doesn't have them, we make this |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
191 # paragraph be a normal action instead. |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
192 fp.restore(snapshot) |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
193 self.has_pending_empty_line = True |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
194 self._aborted = True |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
195 return _ActionState() |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
196 else: |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
197 # Remove wrapping `>`/`<`, and spaces. |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
198 clean_line = clean_line[1:-1].strip() |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
199 |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
200 if RE_EMPTY_LINE.match(fp.peekline()): |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
201 self.text += clean_line |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
202 self.has_pending_empty_line = True |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
203 break |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
204 self.text += clean_line + eol |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
205 |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
206 return ANY_STATE |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
207 |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
208 def exit(self, ctx): |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
209 if not self._aborted: |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
210 ctx.document.lastScene().addCenteredAction(self.text) |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
211 |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
212 |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
213 RE_CHARACTER_LINE = re.compile(r"^\s*[A-Z\-]+\s*(\(.*\))?$", re.M) |
0 | 214 |
215 | |
216 class _CharacterState(FontaineState): | |
217 def match(self, fp, ctx): | |
218 lines = fp.peeklines(3) | |
219 return (RE_EMPTY_LINE.match(lines[0]) and | |
220 RE_CHARACTER_LINE.match(lines[1]) and | |
221 not RE_EMPTY_LINE.match(lines[2])) | |
222 | |
223 def consume(self, fp, ctx): | |
224 fp.readline() # Get past the empty line. | |
225 line = fp.readline().rstrip('\r\n') | |
1
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
226 line = line.lstrip() # Remove indenting. |
0 | 227 line = line.lstrip('@') # In case it was forced. |
228 ctx.document.lastScene().addCharacter(line) | |
229 return [_ParentheticalState, _DialogState] | |
230 | |
231 | |
232 RE_PARENTHETICAL_LINE = re.compile(r"^\s*\(.*\)\s*$", re.M) | |
233 | |
234 | |
235 class _ParentheticalState(FontaineState): | |
236 def match(self, fp, ctx): | |
237 # We only get here from a `_CharacterState` so we know the previous | |
238 # one is already that. | |
239 line = fp.peekline() | |
240 return RE_PARENTHETICAL_LINE.match(line) | |
241 | |
242 def consume(self, fp, ctx): | |
1
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
243 line = fp.readline().lstrip().rstrip('\r\n') |
0 | 244 ctx.document.lastScene().addParenthetical(line) |
1
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
245 |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
246 next_line = fp.peekline() |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
247 if not RE_EMPTY_LINE.match(next_line): |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
248 return _DialogState() |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
249 |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
250 self.has_pending_empty_line = True |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
251 return ANY_STATE |
0 | 252 |
253 | |
254 class _DialogState(FontaineState): | |
255 def __init__(self): | |
256 super().__init__() | |
257 self.text = '' | |
258 | |
259 def match(self, fp, ctx): | |
1
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
260 # We only get here from a `_CharacterState` or `_ParentheticalState` |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
261 # so we just need to check there's some text. |
0 | 262 line = fp.peekline() |
263 return not RE_EMPTY_LINE.match(line) | |
264 | |
265 def consume(self, fp, ctx): | |
266 while True: | |
267 line = fp.readline() | |
268 if not line: | |
269 return EOF_STATE | |
1
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
270 |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
271 line = line.lstrip() # Remove indenting. |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
272 |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
273 # Next we could be either continuing the dialog line, going to |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
274 # a parenthetical, or exiting dialog altogether. |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
275 next_line = fp.peekline() |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
276 |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
277 if RE_PARENTHETICAL_LINE.match(next_line): |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
278 self.text += line.rstrip('\r\n') |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
279 return _ParentheticalState() |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
280 |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
281 if RE_EMPTY_LINE.match(next_line): |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
282 self.text += line.rstrip('\r\n') |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
283 self.has_pending_empty_line = True |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
284 break |
0 | 285 self.text += line |
1
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
286 |
0 | 287 return ANY_STATE |
288 | |
289 def exit(self, ctx): | |
290 ctx.document.lastScene().addDialog(self.text.rstrip('\r\n')) | |
291 | |
292 | |
293 class _LyricsState(FontaineState): | |
1
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
294 def __init__(self): |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
295 super().__init__() |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
296 self.text = '' |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
297 self._aborted = False |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
298 |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
299 # No `match` method, this can only be forced. |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
300 # (see `_ForcedParagraphStates`) |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
301 |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
302 def consume(self, fp, ctx): |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
303 snapshot = fp.snapshot() |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
304 fp.readline() # Get past the empty line. |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
305 while True: |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
306 line = fp.readline() |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
307 if not line: |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
308 return EOF_STATE |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
309 |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
310 if line.startswith('~'): |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
311 line = line.lstrip('~') |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
312 else: |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
313 logger.debug("Rolling back lyrics into action paragraph.") |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
314 fp.restore(snapshot) |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
315 self.has_pending_empty_line = True |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
316 self._aborted = True |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
317 return _ActionState() |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
318 |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
319 if RE_EMPTY_LINE.match(fp.peekline()): |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
320 self.text += line.rstrip('\r\n') |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
321 self.has_pending_empty_line = True |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
322 break |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
323 self.text += line |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
324 |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
325 return ANY_STATE |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
326 |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
327 def exit(self, ctx): |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
328 if not self._aborted: |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
329 ctx.document.lastScene().addLyrics(self.text) |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
330 |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
331 |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
332 RE_TRANSITION_LINE = re.compile(r"^\s*[^a-z]+TO\:$", re.M) |
0 | 333 |
334 | |
335 class _TransitionState(FontaineState): | |
1
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
336 def match(self, fp, ctx): |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
337 lines = fp.peeklines(3) |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
338 return ( |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
339 RE_EMPTY_LINE.match(lines[0]) and |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
340 RE_TRANSITION_LINE.match(lines[1]) and |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
341 RE_EMPTY_LINE.match(lines[2])) |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
342 |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
343 def consume(self, fp, ctx): |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
344 fp.readline() # Get past the empty line. |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
345 line = fp.readline().lstrip().rstrip('\r\n') |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
346 line = line.lstrip('>') # In case it was forced. |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
347 ctx.document.lastScene().addTransition(line) |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
348 self.has_pending_empty_line = True |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
349 |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
350 |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
351 RE_PAGE_BREAK_LINE = re.compile(r"^\=\=\=+$", re.M) |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
352 |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
353 |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
354 class _PageBreakState(FontaineState): |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
355 def match(self, fp, ctx): |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
356 lines = fp.peeklines(3) |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
357 return ( |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
358 RE_EMPTY_LINE.match(lines[0]) and |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
359 RE_PAGE_BREAK_LINE.match(lines[1]) and |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
360 RE_EMPTY_LINE.match(lines[2])) |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
361 |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
362 def consume(self, fp, ctx): |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
363 fp.readline() |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
364 fp.readline() |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
365 ctx.document.lastScene().addPageBreak() |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
366 self.has_pending_empty_line = True |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
367 return ANY_STATE |
0 | 368 |
369 | |
370 class _ForcedParagraphStates(FontaineState): | |
371 STATE_SYMBOLS = { | |
372 '.': _SceneHeaderState, | |
373 '!': _ActionState, | |
374 '@': _CharacterState, | |
375 '~': _LyricsState, | |
376 '>': _TransitionState | |
377 } | |
378 | |
379 def __init__(self): | |
380 super().__init__() | |
381 self._state_cls = None | |
1
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
382 self._consume_empty_line = False |
0 | 383 |
384 def match(self, fp, ctx): | |
385 lines = fp.peeklines(2) | |
1
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
386 symbol = lines[1][:1] |
0 | 387 if (RE_EMPTY_LINE.match(lines[0]) and |
1
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
388 symbol in self.STATE_SYMBOLS): |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
389 # Special case: don't force a transition state if it's |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
390 # really some centered text. |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
391 if symbol == '>' and RE_CENTERED_LINE.match(lines[1]): |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
392 return False |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
393 |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
394 self._state_cls = self.STATE_SYMBOLS[symbol] |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
395 |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
396 # Special case: for forced action paragraphs, don't leave |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
397 # the blank line there. |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
398 if symbol == '!': |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
399 self._consume_empty_line = True |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
400 |
0 | 401 return True |
402 return False | |
403 | |
404 def consume(self, fp, ctx): | |
1
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
405 if self._consume_empty_line: |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
406 fp.readline() |
0 | 407 return self._state_cls() |
408 | |
409 | |
1
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
410 ROOT_STATES = [ |
0 | 411 _ForcedParagraphStates, # Must be first. |
412 _SceneHeaderState, | |
413 _CharacterState, | |
414 _TransitionState, | |
1
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
415 _PageBreakState, |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
416 _CenteredActionState, |
0 | 417 _ActionState, # Must be last. |
418 ] | |
419 | |
420 | |
421 class _PeekableFile: | |
422 def __init__(self, fp): | |
423 self.line_no = 1 | |
424 self._fp = fp | |
1
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
425 self._blankAt0 = False |
0 | 426 |
427 def readline(self, size=-1): | |
1
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
428 if self._blankAt0: |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
429 self._blankAt0 = False |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
430 return '\n' |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
431 |
0 | 432 data = self._fp.readline(size) |
433 self.line_no += 1 | |
434 return data | |
435 | |
436 def peekline(self): | |
1
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
437 if self._blankAt0: |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
438 return '\n' |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
439 |
0 | 440 pos = self._fp.tell() |
441 line = self._fp.readline() | |
442 self._fp.seek(pos) | |
443 return line | |
444 | |
445 def peeklines(self, count): | |
446 pos = self._fp.tell() | |
447 lines = [] | |
1
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
448 if self._blankAt0: |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
449 lines.append('\n') |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
450 count -= 1 |
0 | 451 for i in range(count): |
452 lines.append(self._fp.readline()) | |
453 self._fp.seek(pos) | |
454 return lines | |
455 | |
1
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
456 def snapshot(self): |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
457 return (self._fp.tell(), self._blankAt0, self.line_no) |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
458 |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
459 def restore(self, snapshot): |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
460 self._fp.seek(snapshot[0]) |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
461 self._blankAt0 = snapshot[1] |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
462 self.line_no = snapshot[2] |
0 | 463 |
1
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
464 def _addBlankAt0(self): |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
465 if self._fp.tell() != 0: |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
466 raise Exception( |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
467 "Can't add blank line at 0 if reading has started.") |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
468 self._blankAt0 = True |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
469 |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
470 def _read(self, size, advance_line_no): |
0 | 471 data = self._fp.read(size) |
472 if advance_line_no: | |
473 self.line_no += data.count('\n') | |
474 return data | |
475 | |
476 | |
477 class _FontaineStateMachine: | |
478 def __init__(self, fp, doc): | |
479 self.fp = _PeekableFile(fp) | |
480 self.state = None | |
481 self.document = doc | |
482 | |
483 @property | |
484 def line_no(self): | |
485 return self.fp.line_no | |
486 | |
487 def run(self): | |
1
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
488 # Start with the page title... unless it doesn't match, in which |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
489 # case we start with a "pass through" state that will just return |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
490 # `ANY_STATE` so we can start matching stuff. |
0 | 491 self.state = _TitlePageState() |
1
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
492 if not self.state.match(self.fp, self): |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
493 logger.debug("No title page value found on line 1, " |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
494 "using pass-through state with added blank line.") |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
495 self.state = _PassThroughState() |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
496 if not RE_EMPTY_LINE.match(self.fp.peekline()): |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
497 # Add a fake empty line at the beginning of the text if |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
498 # there's not one already. This makes state matching easier. |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
499 self.fp._addBlankAt0() |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
500 # Make this added empty line "pending" so if the first line |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
501 # is an action paragraph, it doesn't include it. |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
502 self.state.has_pending_empty_line = True |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
503 |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
504 # Start parsing! Here we try to do a mostly-forward-only parser with |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
505 # non overlapping regexes to make it decently fast. |
0 | 506 while True: |
507 logger.debug("State '%s' consuming from '%s'..." % | |
508 (self.state.__class__.__name__, self.fp.peekline())) | |
509 res = self.state.consume(self.fp, self) | |
510 | |
511 # See if we reached the end of the file. | |
512 if not self.fp.peekline(): | |
513 logger.debug("Reached end of line... ending parsing.") | |
514 res = EOF_STATE | |
515 | |
516 # Figure out what to do next... | |
517 | |
518 if res is None: | |
519 raise Exception( | |
520 "States need to return `ANY_STATE`, one or more specific " | |
521 "states, or `EOF_STATE` if they reached the end of the " | |
522 "file.") | |
523 | |
1
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
524 elif res is ANY_STATE or isinstance(res, list): |
0 | 525 # State wants to exit, we need to figure out what is the |
526 # next state. | |
527 pos = self.fp._fp.tell() | |
528 next_states = res | |
529 if next_states is ANY_STATE: | |
1
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
530 next_states = ROOT_STATES |
0 | 531 logger.debug("Trying to match next state from: %s" % |
532 [t.__name__ for t in next_states]) | |
533 for sc in next_states: | |
534 s = sc() | |
535 if s.match(self.fp, self): | |
536 logger.debug("Matched state %s" % | |
537 s.__class__.__name__) | |
538 self.fp._fp.seek(pos) | |
539 res = s | |
540 break | |
541 else: | |
542 raise Exception("Can't match following state after: %s" % | |
543 self.state) | |
1
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
544 |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
545 # Handle the current state before we move on to the new one. |
0 | 546 if self.state: |
547 if type(self.state) == type(res) and self.state.can_merge: | |
548 # Don't switch states if the next state is the same | |
549 # type and that type supports merging. | |
1
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
550 self.state.merge() |
0 | 551 continue |
552 | |
553 self.state.exit(self) | |
1
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
554 if (self.state.has_pending_empty_line and |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
555 not res.needs_pending_empty_lines): |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
556 logger.debug("Skipping pending blank line from %s" % |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
557 self.state.__class__.__name__) |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
558 self.fp.readline() |
0 | 559 |
560 self.state = res | |
561 | |
1
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
562 elif isinstance(res, FontaineState): |
0 | 563 # State wants to exit, wants a specific state to be next. |
564 if self.state: | |
565 self.state.exit(self) | |
1
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
566 if (self.state.has_pending_empty_line and |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
567 not res.needs_pending_empty_lines): |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
568 logger.debug("Skipping pending blank line from %s" % |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
569 self.state.__class__.__name__) |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
570 self.fp.readline() |
0 | 571 self.state = res |
572 | |
1
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
573 elif res is EOF_STATE: |
0 | 574 # Reached end of file. |
575 if self.state: | |
576 self.state.exit(self) | |
577 break | |
578 | |
1
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
579 else: |
74b83e3d921e
Add more states, add more tests.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
580 raise Exception("Unsupported state result: %s" % res) |
0 | 581 |
582 | |
583 class FontaineParser: | |
584 def __init__(self): | |
585 pass | |
586 | |
587 def parse(self, filein): | |
588 if isinstance(filein, str): | |
589 with open(filein, 'r') as fp: | |
590 return self._doParse(fp) | |
591 else: | |
592 return self._doParse(fp) | |
593 | |
594 def parseString(self, text): | |
595 import io | |
596 with io.StringIO(text) as fp: | |
597 return self._doParse(fp) | |
598 | |
599 def _doParse(self, fp): | |
600 from .document import FontaineDocument | |
601 doc = FontaineDocument() | |
602 machine = _FontaineStateMachine(fp, doc) | |
603 machine.run() | |
604 return doc |