entangled-cli 2.4.2__tar.gz → 2.4.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {entangled_cli-2.4.2 → entangled_cli-2.4.3}/PKG-INFO +7 -1
- {entangled_cli-2.4.2 → entangled_cli-2.4.3}/README.md +6 -0
- {entangled_cli-2.4.2 → entangled_cli-2.4.3}/entangled/commands/watch.py +5 -1
- {entangled_cli-2.4.2 → entangled_cli-2.4.3}/entangled/hooks/quarto_attributes.py +13 -2
- {entangled_cli-2.4.2 → entangled_cli-2.4.3}/entangled/interface/document.py +5 -4
- {entangled_cli-2.4.2 → entangled_cli-2.4.3}/entangled/io/filedb.py +4 -1
- {entangled_cli-2.4.2 → entangled_cli-2.4.3}/entangled/io/transaction.py +7 -4
- {entangled_cli-2.4.2 → entangled_cli-2.4.3}/entangled/iterators/lines.py +2 -5
- {entangled_cli-2.4.2 → entangled_cli-2.4.3}/entangled/model/tangle.py +14 -10
- {entangled_cli-2.4.2 → entangled_cli-2.4.3}/entangled/parsing.py +13 -2
- {entangled_cli-2.4.2 → entangled_cli-2.4.3}/entangled/readers/code.py +13 -11
- {entangled_cli-2.4.2 → entangled_cli-2.4.3}/pyproject.toml +1 -1
- {entangled_cli-2.4.2 → entangled_cli-2.4.3}/.gitignore +0 -0
- {entangled_cli-2.4.2 → entangled_cli-2.4.3}/LICENSE +0 -0
- {entangled_cli-2.4.2 → entangled_cli-2.4.3}/entangled/__init__.py +0 -0
- {entangled_cli-2.4.2 → entangled_cli-2.4.3}/entangled/commands/__init__.py +0 -0
- {entangled_cli-2.4.2 → entangled_cli-2.4.3}/entangled/commands/brei.py +0 -0
- {entangled_cli-2.4.2 → entangled_cli-2.4.3}/entangled/commands/main.py +0 -0
- {entangled_cli-2.4.2 → entangled_cli-2.4.3}/entangled/commands/new.py +0 -0
- {entangled_cli-2.4.2 → entangled_cli-2.4.3}/entangled/commands/reset.py +0 -0
- {entangled_cli-2.4.2 → entangled_cli-2.4.3}/entangled/commands/status.py +0 -0
- {entangled_cli-2.4.2 → entangled_cli-2.4.3}/entangled/commands/stitch.py +0 -0
- {entangled_cli-2.4.2 → entangled_cli-2.4.3}/entangled/commands/sync.py +0 -0
- {entangled_cli-2.4.2 → entangled_cli-2.4.3}/entangled/commands/tangle.py +0 -0
- {entangled_cli-2.4.2 → entangled_cli-2.4.3}/entangled/config/__init__.py +0 -0
- {entangled_cli-2.4.2 → entangled_cli-2.4.3}/entangled/config/annotation_method.py +0 -0
- {entangled_cli-2.4.2 → entangled_cli-2.4.3}/entangled/config/config_data.py +0 -0
- {entangled_cli-2.4.2 → entangled_cli-2.4.3}/entangled/config/config_update.py +0 -0
- {entangled_cli-2.4.2 → entangled_cli-2.4.3}/entangled/config/document_style.py +0 -0
- {entangled_cli-2.4.2 → entangled_cli-2.4.3}/entangled/config/language.py +0 -0
- {entangled_cli-2.4.2 → entangled_cli-2.4.3}/entangled/config/markers.py +0 -0
- {entangled_cli-2.4.2 → entangled_cli-2.4.3}/entangled/config/namespace_default.py +0 -0
- {entangled_cli-2.4.2 → entangled_cli-2.4.3}/entangled/config/templates.py +0 -0
- {entangled_cli-2.4.2 → entangled_cli-2.4.3}/entangled/config/version.py +0 -0
- {entangled_cli-2.4.2 → entangled_cli-2.4.3}/entangled/errors/__init__.py +0 -0
- {entangled_cli-2.4.2 → entangled_cli-2.4.3}/entangled/errors/internal.py +0 -0
- {entangled_cli-2.4.2 → entangled_cli-2.4.3}/entangled/errors/user.py +0 -0
- {entangled_cli-2.4.2 → entangled_cli-2.4.3}/entangled/hooks/__init__.py +0 -0
- {entangled_cli-2.4.2 → entangled_cli-2.4.3}/entangled/hooks/base.py +0 -0
- {entangled_cli-2.4.2 → entangled_cli-2.4.3}/entangled/hooks/build.py +0 -0
- {entangled_cli-2.4.2 → entangled_cli-2.4.3}/entangled/hooks/repl.py +0 -0
- {entangled_cli-2.4.2 → entangled_cli-2.4.3}/entangled/hooks/shebang.py +0 -0
- {entangled_cli-2.4.2 → entangled_cli-2.4.3}/entangled/hooks/spdx_license.py +0 -0
- {entangled_cli-2.4.2 → entangled_cli-2.4.3}/entangled/hooks/task.py +0 -0
- {entangled_cli-2.4.2 → entangled_cli-2.4.3}/entangled/interface/__init__.py +0 -0
- {entangled_cli-2.4.2 → entangled_cli-2.4.3}/entangled/interface/context.py +0 -0
- {entangled_cli-2.4.2 → entangled_cli-2.4.3}/entangled/io/__init__.py +0 -0
- {entangled_cli-2.4.2 → entangled_cli-2.4.3}/entangled/io/stat.py +0 -0
- {entangled_cli-2.4.2 → entangled_cli-2.4.3}/entangled/io/virtual.py +0 -0
- {entangled_cli-2.4.2 → entangled_cli-2.4.3}/entangled/iterators/__init__.py +0 -0
- {entangled_cli-2.4.2 → entangled_cli-2.4.3}/entangled/iterators/peekable.py +0 -0
- {entangled_cli-2.4.2 → entangled_cli-2.4.3}/entangled/iterators/run_generator.py +0 -0
- {entangled_cli-2.4.2 → entangled_cli-2.4.3}/entangled/logging.py +0 -0
- {entangled_cli-2.4.2 → entangled_cli-2.4.3}/entangled/main.py +0 -0
- {entangled_cli-2.4.2 → entangled_cli-2.4.3}/entangled/model/__init__.py +0 -0
- {entangled_cli-2.4.2 → entangled_cli-2.4.3}/entangled/model/code_block.py +0 -0
- {entangled_cli-2.4.2 → entangled_cli-2.4.3}/entangled/model/content.py +0 -0
- {entangled_cli-2.4.2 → entangled_cli-2.4.3}/entangled/model/properties.py +0 -0
- {entangled_cli-2.4.2 → entangled_cli-2.4.3}/entangled/model/reference_id.py +0 -0
- {entangled_cli-2.4.2 → entangled_cli-2.4.3}/entangled/model/reference_map.py +0 -0
- {entangled_cli-2.4.2 → entangled_cli-2.4.3}/entangled/model/reference_name.py +0 -0
- {entangled_cli-2.4.2 → entangled_cli-2.4.3}/entangled/py.typed +0 -0
- {entangled_cli-2.4.2 → entangled_cli-2.4.3}/entangled/readers/__init__.py +0 -0
- {entangled_cli-2.4.2 → entangled_cli-2.4.3}/entangled/readers/delimiters.py +0 -0
- {entangled_cli-2.4.2 → entangled_cli-2.4.3}/entangled/readers/markdown.py +0 -0
- {entangled_cli-2.4.2 → entangled_cli-2.4.3}/entangled/readers/types.py +0 -0
- {entangled_cli-2.4.2 → entangled_cli-2.4.3}/entangled/readers/yaml_header.py +0 -0
- {entangled_cli-2.4.2 → entangled_cli-2.4.3}/entangled/status.py +0 -0
- {entangled_cli-2.4.2 → entangled_cli-2.4.3}/entangled/text_location.py +0 -0
- {entangled_cli-2.4.2 → entangled_cli-2.4.3}/entangled/utility.py +0 -0
- {entangled_cli-2.4.2 → entangled_cli-2.4.3}/entangled/version.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: entangled-cli
|
|
3
|
-
Version: 2.4.
|
|
3
|
+
Version: 2.4.3
|
|
4
4
|
Summary: Literate Programming toolbox
|
|
5
5
|
Project-URL: Homepage, https://entangled.github.io/
|
|
6
6
|
Project-URL: Repository, https://github.com/entangled/entangled.py
|
|
@@ -58,6 +58,12 @@ Or, if you prefer plain old `pip`,
|
|
|
58
58
|
pip install entangled-cli
|
|
59
59
|
```
|
|
60
60
|
|
|
61
|
+
Or, if you prefer the nix package manager:
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
nix run github:entangled/entangled.py
|
|
65
|
+
```
|
|
66
|
+
|
|
61
67
|
## Use
|
|
62
68
|
|
|
63
69
|
Run the `entangled watch` daemon in the root of your project folder. By default all Markdown files are monitored for fenced code blocks like so:
|
|
@@ -33,6 +33,12 @@ Or, if you prefer plain old `pip`,
|
|
|
33
33
|
pip install entangled-cli
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
+
Or, if you prefer the nix package manager:
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
nix run github:entangled/entangled.py
|
|
40
|
+
```
|
|
41
|
+
|
|
36
42
|
## Use
|
|
37
43
|
|
|
38
44
|
Run the `entangled watch` daemon in the root of your project folder. By default all Markdown files are monitored for fenced code blocks like so:
|
|
@@ -6,6 +6,7 @@ from ..logging import logger
|
|
|
6
6
|
|
|
7
7
|
from .sync import run_sync
|
|
8
8
|
from .main import main
|
|
9
|
+
from ..errors.user import UserError
|
|
9
10
|
|
|
10
11
|
import watchfiles
|
|
11
12
|
|
|
@@ -38,7 +39,10 @@ def _watch(_stop_event: Event | None = None, _start_event: Event | None = None):
|
|
|
38
39
|
|
|
39
40
|
for changes in watchfiles.watch(dirs, stop_event=_stop_event, watch_filter=watch_filter):
|
|
40
41
|
log.debug(changes)
|
|
41
|
-
|
|
42
|
+
try:
|
|
43
|
+
run_sync()
|
|
44
|
+
except UserError as e:
|
|
45
|
+
logger().error(e, exc_info=False)
|
|
42
46
|
|
|
43
47
|
|
|
44
48
|
@main.command()
|
|
@@ -14,16 +14,27 @@ from ..logging import logger
|
|
|
14
14
|
|
|
15
15
|
log = logger()
|
|
16
16
|
|
|
17
|
+
# Cache for compiled regex patterns (keyed by comment opener)
|
|
18
|
+
_yaml_header_pattern_cache: dict[str, re.Pattern[str]] = {}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _get_yaml_header_pattern(comment_open: str) -> re.Pattern[str]:
|
|
22
|
+
"""Get or create a cached compiled pattern for YAML header matching."""
|
|
23
|
+
if comment_open not in _yaml_header_pattern_cache:
|
|
24
|
+
pattern = re.escape(comment_open) + r"\s*\|(.*)"
|
|
25
|
+
_yaml_header_pattern_cache[comment_open] = re.compile(pattern)
|
|
26
|
+
return _yaml_header_pattern_cache[comment_open]
|
|
27
|
+
|
|
17
28
|
|
|
18
29
|
def split_yaml_header(language: Language, source: str) -> tuple[str, str, object]:
|
|
19
30
|
"""Split source into YAML header and body."""
|
|
20
|
-
|
|
31
|
+
pattern = _get_yaml_header_pattern(language.comment.open)
|
|
21
32
|
lines = source.splitlines(keepends=True)
|
|
22
33
|
header_lines: list[str] = []
|
|
23
34
|
body_start: int = 0
|
|
24
35
|
|
|
25
36
|
for i, line in enumerate(lines):
|
|
26
|
-
if m :=
|
|
37
|
+
if m := pattern.match(line):
|
|
27
38
|
header_lines.append(m.group(1))
|
|
28
39
|
continue
|
|
29
40
|
|
|
@@ -35,14 +35,15 @@ class Document:
|
|
|
35
35
|
return get_input_files(self.context.fs, self.config)
|
|
36
36
|
|
|
37
37
|
def source_text(self, path: Path) -> tuple[str, set[PurePath]]:
|
|
38
|
-
deps = set()
|
|
39
|
-
|
|
38
|
+
deps: set[PurePath] = set()
|
|
39
|
+
# Use list for O(n) instead of O(n²) string concatenation
|
|
40
|
+
text_parts: list[str] = []
|
|
40
41
|
for content in self.content[path]:
|
|
41
42
|
t, d = content_to_text(self.reference_map, content)
|
|
42
43
|
if d is not None:
|
|
43
44
|
deps.add(d)
|
|
44
|
-
|
|
45
|
-
return
|
|
45
|
+
text_parts.append(t)
|
|
46
|
+
return "".join(text_parts), deps
|
|
46
47
|
|
|
47
48
|
def target_text(self, path: PurePath) -> tuple[str, set[PurePath]]:
|
|
48
49
|
ref_name = self.reference_map.select_by_target(path)
|
|
@@ -50,8 +50,11 @@ class FileDB(Struct):
|
|
|
50
50
|
return {Path(p) for p in self.targets}
|
|
51
51
|
|
|
52
52
|
def changed_files(self, fs: AbstractFileCache) -> Generator[Path]:
|
|
53
|
+
# A tracked file that no longer exists (e.g. a source that was moved or
|
|
54
|
+
# deleted) counts as changed. Without this guard `fs[Path(p)]` would
|
|
55
|
+
# raise `FileNotFoundError` and crash, see issue #88.
|
|
53
56
|
return (Path(p) for p, known_stat in self.files.items()
|
|
54
|
-
if fs[Path(p)].stat != known_stat)
|
|
57
|
+
if Path(p) not in fs or fs[Path(p)].stat != known_stat)
|
|
55
58
|
|
|
56
59
|
def create_target(self, fs: AbstractFileCache, path: Path):
|
|
57
60
|
if path.is_absolute():
|
|
@@ -93,12 +93,15 @@ class Write(WriterBase):
|
|
|
93
93
|
def conflict(self, fs: AbstractFileCache, db: FileDB) -> Conflict | None:
|
|
94
94
|
if self.target not in fs:
|
|
95
95
|
return None
|
|
96
|
+
# If the file on disk matches the filedb record, it is exactly what
|
|
97
|
+
# Entangled wrote last time and was not edited outside of Entangled, so
|
|
98
|
+
# it is safe to overwrite. Comparing against the filedb is more reliable
|
|
99
|
+
# than comparing modification times against the current set of sources:
|
|
100
|
+
# the latter gives false positives when the dependency graph changes,
|
|
101
|
+
# e.g. when a target no longer depends on one of its former sources
|
|
102
|
+
# (see issue #96).
|
|
96
103
|
if fs[self.target].stat != db[self.target]:
|
|
97
104
|
return Conflict(self.target, "changed outside the control of Entangled")
|
|
98
|
-
if self.sources:
|
|
99
|
-
if all(fs[s].stat < fs[self.target].stat for s in self.sources):
|
|
100
|
-
return Conflict(self.target, "newer than all of its sources: " + ", ".join(
|
|
101
|
-
f"`{s}`" for s in set(self.sources)))
|
|
102
105
|
return None
|
|
103
106
|
|
|
104
107
|
@override
|
|
@@ -10,19 +10,16 @@ type InputToken = tuple[TextLocation, str]
|
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
def lines(text: str) -> Generator[str]:
|
|
13
|
+
"""Iterate over lines in text, preserving newlines."""
|
|
13
14
|
pos = 0
|
|
14
15
|
while (next_pos := text.find("\n", pos)) != -1:
|
|
15
16
|
yield text[pos:next_pos + 1]
|
|
16
17
|
pos = next_pos + 1
|
|
17
|
-
|
|
18
18
|
yield text[pos:]
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
@peekable
|
|
22
22
|
def numbered_lines(filename: PurePath, text: str) -> Generator[InputToken]:
|
|
23
|
-
"""
|
|
24
|
-
Iterate the lines in a file. Doesn't strip newlines. Works with both
|
|
25
|
-
Windows and Unix line endings.
|
|
26
|
-
"""
|
|
23
|
+
"""Iterate the lines in a file. Doesn't strip newlines."""
|
|
27
24
|
for n, line in enumerate(lines(text)):
|
|
28
25
|
yield (TextLocation(filename, n+1), line)
|
|
@@ -4,7 +4,6 @@ from contextlib import contextmanager
|
|
|
4
4
|
from pathlib import PurePath
|
|
5
5
|
|
|
6
6
|
import re
|
|
7
|
-
import os
|
|
8
7
|
from typing import override
|
|
9
8
|
|
|
10
9
|
|
|
@@ -21,6 +20,10 @@ from .reference_name import ReferenceName
|
|
|
21
20
|
|
|
22
21
|
log = logger()
|
|
23
22
|
|
|
23
|
+
# Pre-compiled regex for reference detection (e.g., " <<refname>>")
|
|
24
|
+
_REF_PATTERN = re.compile(r"^(?P<indent>\s*)<<(?P<refname>[\w:/_.-]+)>>\s*$")
|
|
25
|
+
|
|
26
|
+
|
|
24
27
|
@dataclass
|
|
25
28
|
class CyclicReference(UserError):
|
|
26
29
|
ref_name: str
|
|
@@ -89,7 +92,7 @@ def naked_tangler(refs: ReferenceMap) -> Tangler:
|
|
|
89
92
|
|
|
90
93
|
with visitor.visit(ref):
|
|
91
94
|
for line in lines(code_block.source):
|
|
92
|
-
if m :=
|
|
95
|
+
if m := _REF_PATTERN.match(line.rstrip()):
|
|
93
96
|
ref_name = ReferenceName.from_str(m["refname"], code_block.namespace)
|
|
94
97
|
log.debug(f"tangling reference `{ref_name}`")
|
|
95
98
|
if not refs.has_name(ref_name):
|
|
@@ -146,13 +149,14 @@ def tangle_ref(
|
|
|
146
149
|
raise KeyError(name)
|
|
147
150
|
tangler = tanglers[annotation](refs)
|
|
148
151
|
deps: set[PurePath] = set()
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
152
|
+
|
|
153
|
+
def all_lines():
|
|
154
|
+
ref_lst = refs.select_by_name(name)
|
|
155
|
+
yield from tangler(tangler, deps, ref_lst[0], False, True)
|
|
156
|
+
for ref in ref_lst[1:]:
|
|
157
|
+
yield from tangler(tangler, deps, ref, False, False)
|
|
158
|
+
|
|
159
|
+
# Use join for O(n) instead of O(n²) string concatenation
|
|
160
|
+
out = "".join(all_lines())
|
|
157
161
|
|
|
158
162
|
return out, deps
|
|
@@ -14,6 +14,17 @@ from typing import (
|
|
|
14
14
|
import re
|
|
15
15
|
|
|
16
16
|
|
|
17
|
+
# Cache for compiled regex patterns (avoids re-compilation)
|
|
18
|
+
_pattern_cache: dict[str, re.Pattern[str]] = {}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _cached_pattern(regex: str) -> re.Pattern[str]:
|
|
22
|
+
"""Get or create a cached compiled pattern."""
|
|
23
|
+
if regex not in _pattern_cache:
|
|
24
|
+
_pattern_cache[regex] = re.compile(f"^{regex}")
|
|
25
|
+
return _pattern_cache[regex]
|
|
26
|
+
|
|
27
|
+
|
|
17
28
|
@dataclass
|
|
18
29
|
class Failure(Exception):
|
|
19
30
|
"""Base class for parser failures."""
|
|
@@ -198,7 +209,7 @@ def many[T](p: Parser[T]) -> Parser[list[T]]:
|
|
|
198
209
|
|
|
199
210
|
|
|
200
211
|
def matching(regex: str) -> Parser[tuple[str, ...]]:
|
|
201
|
-
pattern =
|
|
212
|
+
pattern = _cached_pattern(regex)
|
|
202
213
|
|
|
203
214
|
@parser
|
|
204
215
|
def _matching(inp: str) -> tuple[tuple[str, ...], str]:
|
|
@@ -210,7 +221,7 @@ def matching(regex: str) -> Parser[tuple[str, ...]]:
|
|
|
210
221
|
|
|
211
222
|
|
|
212
223
|
def fullmatch(regex: str) -> Parser[str]:
|
|
213
|
-
pattern =
|
|
224
|
+
pattern = _cached_pattern(regex)
|
|
214
225
|
|
|
215
226
|
@parser
|
|
216
227
|
def _fullmatch(inp: str):
|
|
@@ -17,7 +17,11 @@ class Block:
|
|
|
17
17
|
content: str
|
|
18
18
|
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
# Pre-compile regex patterns at module level (avoid re-compilation on every call)
|
|
21
|
+
_OPEN_BLOCK_PATTERN = re.compile(
|
|
22
|
+
r"^(?P<indent>\s*).* ~/~ begin <<(?P<source>[^#<>]+)#(?P<ref_name>[^#<>]+)>>\[(?P<ref_count>\d+|init)\]"
|
|
23
|
+
)
|
|
24
|
+
_CLOSE_BLOCK_PATTERN = re.compile(r"^(?P<indent>\s*).* ~/~ end")
|
|
21
25
|
|
|
22
26
|
|
|
23
27
|
@dataclass
|
|
@@ -28,7 +32,7 @@ class OpenBlockData:
|
|
|
28
32
|
|
|
29
33
|
|
|
30
34
|
def open_block(line: str) -> OpenBlockData | None:
|
|
31
|
-
if not (m :=
|
|
35
|
+
if not (m := _OPEN_BLOCK_PATTERN.match(line)):
|
|
32
36
|
return None
|
|
33
37
|
|
|
34
38
|
ref_name = ReferenceName.from_str(m["ref_name"])
|
|
@@ -38,16 +42,13 @@ def open_block(line: str) -> OpenBlockData | None:
|
|
|
38
42
|
return OpenBlockData(ReferenceId(ref_name, md_source, ref_count), is_init, m["indent"])
|
|
39
43
|
|
|
40
44
|
|
|
41
|
-
CLOSE_BLOCK_EXPR = r"^(?P<indent>\s*).* ~/~ end"
|
|
42
|
-
|
|
43
|
-
|
|
44
45
|
@dataclass
|
|
45
46
|
class CloseBlockData:
|
|
46
47
|
indent: str
|
|
47
48
|
|
|
48
49
|
|
|
49
50
|
def close_block(line: str) -> CloseBlockData | None:
|
|
50
|
-
if not (m :=
|
|
51
|
+
if not (m := _CLOSE_BLOCK_PATTERN.match(line)):
|
|
51
52
|
return None
|
|
52
53
|
return CloseBlockData(m["indent"])
|
|
53
54
|
|
|
@@ -76,25 +77,26 @@ def read_block(namespace: tuple[str, ...], indent: str, input: InputStream) -> G
|
|
|
76
77
|
if block_data.indent < indent:
|
|
77
78
|
raise IndentationError(pos)
|
|
78
79
|
|
|
79
|
-
|
|
80
|
+
# Use list for O(n) instead of O(n²) string concatenation
|
|
81
|
+
content_parts: list[str] = []
|
|
80
82
|
while input:
|
|
81
83
|
line = yield from read_block(block_data.ref.name.namespace, block_data.indent, input)
|
|
82
84
|
if line is not None:
|
|
83
|
-
|
|
85
|
+
content_parts.append(line)
|
|
84
86
|
continue
|
|
85
87
|
|
|
86
88
|
pos, line = next(input)
|
|
87
89
|
if (close_block_data := close_block(line)) is None:
|
|
88
90
|
if not line.strip():
|
|
89
|
-
|
|
91
|
+
content_parts.append(line.lstrip(" \t"))
|
|
90
92
|
elif not line.startswith(block_data.indent):
|
|
91
93
|
raise IndentationError(pos)
|
|
92
94
|
else:
|
|
93
|
-
|
|
95
|
+
content_parts.append(line.removeprefix(block_data.indent))
|
|
94
96
|
else:
|
|
95
97
|
if close_block_data.indent != block_data.indent:
|
|
96
98
|
raise IndentationError(pos)
|
|
97
|
-
yield Block(block_data.ref,
|
|
99
|
+
yield Block(block_data.ref, "".join(content_parts))
|
|
98
100
|
|
|
99
101
|
if block_data.is_init:
|
|
100
102
|
extra_indent = block_data.indent.removeprefix(indent)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|