| 170 | |
| 171 | |
| 172 | class IndentStack: |
| 173 | def __init__(self) -> None: |
| 174 | self.indents: list[int] = [] |
| 175 | self.margin: str | None = None |
| 176 | |
| 177 | def _ensure(self) -> None: |
| 178 | if not self.indents: |
| 179 | fail('IndentStack expected indents, but none are defined.') |
| 180 | |
| 181 | def measure(self, line: str) -> int: |
| 182 | """ |
| 183 | Returns the length of the line's margin. |
| 184 | """ |
| 185 | if '\t' in line: |
| 186 | fail('Tab characters are illegal in the Argument Clinic DSL.') |
| 187 | stripped = line.lstrip() |
| 188 | if not len(stripped): |
| 189 | # we can't tell anything from an empty line |
| 190 | # so just pretend it's indented like our current indent |
| 191 | self._ensure() |
| 192 | return self.indents[-1] |
| 193 | return len(line) - len(stripped) |
| 194 | |
| 195 | def infer(self, line: str) -> int: |
| 196 | """ |
| 197 | Infer what is now the current margin based on this line. |
| 198 | Returns: |
| 199 | 1 if we have indented (or this is the first margin) |
| 200 | 0 if the margin has not changed |
| 201 | -N if we have dedented N times |
| 202 | """ |
| 203 | indent = self.measure(line) |
| 204 | margin = ' ' * indent |
| 205 | if not self.indents: |
| 206 | self.indents.append(indent) |
| 207 | self.margin = margin |
| 208 | return 1 |
| 209 | current = self.indents[-1] |
| 210 | if indent == current: |
| 211 | return 0 |
| 212 | if indent > current: |
| 213 | self.indents.append(indent) |
| 214 | self.margin = margin |
| 215 | return 1 |
| 216 | # indent < current |
| 217 | if indent not in self.indents: |
| 218 | fail("Illegal outdent.") |
| 219 | outdent_count = 0 |
| 220 | while indent != current: |
| 221 | self.indents.pop() |
| 222 | current = self.indents[-1] |
| 223 | outdent_count -= 1 |
| 224 | self.margin = margin |
| 225 | return outdent_count |
| 226 | |
| 227 | @property |
| 228 | def depth(self) -> int: |
| 229 | """ |
no outgoing calls
no test coverage detected
searching dependent graphs…