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