labnotes 0.0.47__tar.gz → 0.0.48__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.
- {labnotes-0.0.47 → labnotes-0.0.48}/PKG-INFO +1 -1
- {labnotes-0.0.47 → labnotes-0.0.48}/src/labnotes/context.md +5 -3
- {labnotes-0.0.47 → labnotes-0.0.48}/src/labnotes/runner/execute_run.py +0 -2
- {labnotes-0.0.47 → labnotes-0.0.48}/src/labnotes/runner/run_experiment.py +10 -5
- {labnotes-0.0.47 → labnotes-0.0.48}/src/labnotes/tables/path_glob.py +1 -1
- {labnotes-0.0.47 → labnotes-0.0.48}/src/labnotes/tables/path_pattern.py +3 -0
- {labnotes-0.0.47 → labnotes-0.0.48}/src/labnotes/tui/run_app.py +4 -2
- {labnotes-0.0.47 → labnotes-0.0.48}/testing-conventions.toml +7 -1
- labnotes-0.0.48/tests/integration/conftest.py +83 -0
- labnotes-0.0.47/src/labnotes/runner/new_invocation_id.py +0 -6
- {labnotes-0.0.47 → labnotes-0.0.48}/.gitignore +0 -0
- {labnotes-0.0.47 → labnotes-0.0.48}/CHANGELOG.md +0 -0
- {labnotes-0.0.47 → labnotes-0.0.48}/MIGRATIONS.md +0 -0
- {labnotes-0.0.47 → labnotes-0.0.48}/README.md +0 -0
- {labnotes-0.0.47 → labnotes-0.0.48}/pyproject.toml +0 -0
- {labnotes-0.0.47 → labnotes-0.0.48}/src/labnotes/__init__.py +0 -0
- {labnotes-0.0.47 → labnotes-0.0.48}/src/labnotes/asset_url.py +0 -0
- {labnotes-0.0.47 → labnotes-0.0.48}/src/labnotes/cell_progress.py +0 -0
- {labnotes-0.0.47 → labnotes-0.0.48}/src/labnotes/check_counts.py +0 -0
- {labnotes-0.0.47 → labnotes-0.0.48}/src/labnotes/cli.py +0 -0
- {labnotes-0.0.47 → labnotes-0.0.48}/src/labnotes/context_guide.py +0 -0
- {labnotes-0.0.47 → labnotes-0.0.48}/src/labnotes/count_runs.py +0 -0
- {labnotes-0.0.47 → labnotes-0.0.48}/src/labnotes/create_app.py +0 -0
- {labnotes-0.0.47 → labnotes-0.0.48}/src/labnotes/create_experiment.py +0 -0
- {labnotes-0.0.47 → labnotes-0.0.48}/src/labnotes/data_html.py +0 -0
- {labnotes-0.0.47 → labnotes-0.0.48}/src/labnotes/default_config_text.py +0 -0
- {labnotes-0.0.47 → labnotes-0.0.48}/src/labnotes/default_counts.py +0 -0
- {labnotes-0.0.47 → labnotes-0.0.48}/src/labnotes/drain_watch.py +0 -0
- {labnotes-0.0.47 → labnotes-0.0.48}/src/labnotes/experiment_notes.py +0 -0
- {labnotes-0.0.47 → labnotes-0.0.48}/src/labnotes/experiment_progress.py +0 -0
- {labnotes-0.0.47 → labnotes-0.0.48}/src/labnotes/experiment_runs.py +0 -0
- {labnotes-0.0.47 → labnotes-0.0.48}/src/labnotes/experiments.py +0 -0
- {labnotes-0.0.47 → labnotes-0.0.48}/src/labnotes/format_value.py +0 -0
- {labnotes-0.0.47 → labnotes-0.0.48}/src/labnotes/init.py +0 -0
- {labnotes-0.0.47 → labnotes-0.0.48}/src/labnotes/is_positive_int.py +0 -0
- {labnotes-0.0.47 → labnotes-0.0.48}/src/labnotes/list_experiments.py +0 -0
- {labnotes-0.0.47 → labnotes-0.0.48}/src/labnotes/list_runs.py +0 -0
- {labnotes-0.0.47 → labnotes-0.0.48}/src/labnotes/load_config.py +0 -0
- {labnotes-0.0.47 → labnotes-0.0.48}/src/labnotes/load_experiment.py +0 -0
- {labnotes-0.0.47 → labnotes-0.0.48}/src/labnotes/models.py +0 -0
- {labnotes-0.0.47 → labnotes-0.0.48}/src/labnotes/modes.py +0 -0
- {labnotes-0.0.47 → labnotes-0.0.48}/src/labnotes/notebook_kernel.py +0 -0
- {labnotes-0.0.47 → labnotes-0.0.48}/src/labnotes/output_html.py +0 -0
- {labnotes-0.0.47 → labnotes-0.0.48}/src/labnotes/parse_run_view.py +0 -0
- {labnotes-0.0.47 → labnotes-0.0.48}/src/labnotes/print_rows.py +0 -0
- {labnotes-0.0.47 → labnotes-0.0.48}/src/labnotes/python_blocks.py +0 -0
- {labnotes-0.0.47 → labnotes-0.0.48}/src/labnotes/read_config.py +0 -0
- {labnotes-0.0.47 → labnotes-0.0.48}/src/labnotes/read_path.py +0 -0
- {labnotes-0.0.47 → labnotes-0.0.48}/src/labnotes/render_markdown.py +0 -0
- {labnotes-0.0.47 → labnotes-0.0.48}/src/labnotes/runner/__init__.py +0 -0
- {labnotes-0.0.47 → labnotes-0.0.48}/src/labnotes/runner/describe_event.py +0 -0
- {labnotes-0.0.47 → labnotes-0.0.48}/src/labnotes/runner/hold_folder.py +0 -0
- {labnotes-0.0.47 → labnotes-0.0.48}/src/labnotes/runner/new_run_id.py +0 -0
- {labnotes-0.0.47 → labnotes-0.0.48}/src/labnotes/runner/new_stamped_run_id.py +0 -0
- {labnotes-0.0.47 → labnotes-0.0.48}/src/labnotes/runner/param_args.py +0 -0
- {labnotes-0.0.47 → labnotes-0.0.48}/src/labnotes/runner/param_grid.py +0 -0
- {labnotes-0.0.47 → labnotes-0.0.48}/src/labnotes/runner/plan_runs.py +0 -0
- {labnotes-0.0.47 → labnotes-0.0.48}/src/labnotes/runner/read_experiment_config.py +0 -0
- {labnotes-0.0.47 → labnotes-0.0.48}/src/labnotes/runner/run_folder.py +0 -0
- {labnotes-0.0.47 → labnotes-0.0.48}/src/labnotes/runner/run_headless.py +0 -0
- {labnotes-0.0.47 → labnotes-0.0.48}/src/labnotes/runner/runs_parent.py +0 -0
- {labnotes-0.0.47 → labnotes-0.0.48}/src/labnotes/runner/shell_command.py +0 -0
- {labnotes-0.0.47 → labnotes-0.0.48}/src/labnotes/runner/start_experiment.py +0 -0
- {labnotes-0.0.47 → labnotes-0.0.48}/src/labnotes/runner/stop_then_kill.py +0 -0
- {labnotes-0.0.47 → labnotes-0.0.48}/src/labnotes/runner/wait_for_group.py +0 -0
- {labnotes-0.0.47 → labnotes-0.0.48}/src/labnotes/runner/write_run_json.py +0 -0
- {labnotes-0.0.47 → labnotes-0.0.48}/src/labnotes/saved_outputs.py +0 -0
- {labnotes-0.0.47 → labnotes-0.0.48}/src/labnotes/serve_app.py +0 -0
- {labnotes-0.0.47 → labnotes-0.0.48}/src/labnotes/server.py +0 -0
- {labnotes-0.0.47 → labnotes-0.0.48}/src/labnotes/sort_href.py +0 -0
- {labnotes-0.0.47 → labnotes-0.0.48}/src/labnotes/sql_quote.py +0 -0
- {labnotes-0.0.47 → labnotes-0.0.48}/src/labnotes/static/experiment.js +0 -0
- {labnotes-0.0.47 → labnotes-0.0.48}/src/labnotes/static/labnotes.css +0 -0
- {labnotes-0.0.47 → labnotes-0.0.48}/src/labnotes/static/row-link.js +0 -0
- {labnotes-0.0.47 → labnotes-0.0.48}/src/labnotes/static/run-cell.js +0 -0
- {labnotes-0.0.47 → labnotes-0.0.48}/src/labnotes/static/view-toggle.js +0 -0
- {labnotes-0.0.47 → labnotes-0.0.48}/src/labnotes/stop_idle_kernels.py +0 -0
- {labnotes-0.0.47 → labnotes-0.0.48}/src/labnotes/summarize_config.py +0 -0
- {labnotes-0.0.47 → labnotes-0.0.48}/src/labnotes/tables/__init__.py +0 -0
- {labnotes-0.0.47 → labnotes-0.0.48}/src/labnotes/tables/experiment_rows.py +0 -0
- {labnotes-0.0.47 → labnotes-0.0.48}/src/labnotes/tables/file_prefix.py +0 -0
- {labnotes-0.0.47 → labnotes-0.0.48}/src/labnotes/tables/iso_time.py +0 -0
- {labnotes-0.0.47 → labnotes-0.0.48}/src/labnotes/tables/list_experiment_runs.py +0 -0
- {labnotes-0.0.47 → labnotes-0.0.48}/src/labnotes/tables/open_tables.py +0 -0
- {labnotes-0.0.47 → labnotes-0.0.48}/src/labnotes/tables/read_experiment.py +0 -0
- {labnotes-0.0.47 → labnotes-0.0.48}/src/labnotes/tables/read_file.py +0 -0
- {labnotes-0.0.47 → labnotes-0.0.48}/src/labnotes/tables/read_run.py +0 -0
- {labnotes-0.0.47 → labnotes-0.0.48}/src/labnotes/tables/summarize_experiments.py +0 -0
- {labnotes-0.0.47 → labnotes-0.0.48}/src/labnotes/templates/base.html +0 -0
- {labnotes-0.0.47 → labnotes-0.0.48}/src/labnotes/templates/experiment.html +0 -0
- {labnotes-0.0.47 → labnotes-0.0.48}/src/labnotes/templates/index.html +0 -0
- {labnotes-0.0.47 → labnotes-0.0.48}/src/labnotes/templates/macros.html +0 -0
- {labnotes-0.0.47 → labnotes-0.0.48}/src/labnotes/tui/__init__.py +0 -0
- {labnotes-0.0.47 → labnotes-0.0.48}/src/labnotes/tui/dim.py +0 -0
- {labnotes-0.0.47 → labnotes-0.0.48}/src/labnotes/tui/format_elapsed.py +0 -0
- {labnotes-0.0.47 → labnotes-0.0.48}/src/labnotes/tui/run_tui.py +0 -0
- {labnotes-0.0.47 → labnotes-0.0.48}/src/labnotes/tui/runs_table.py +0 -0
- {labnotes-0.0.47 → labnotes-0.0.48}/uv.lock +0 -0
|
@@ -62,14 +62,16 @@ Each combination of `[params]` runs `repeats` times (1 if omitted), round-robin,
|
|
|
62
62
|
so running again fills in what failed or never ran. `mode` under
|
|
63
63
|
`[experiments]` in `.labnotes.toml` sets it for every experiment that doesn't
|
|
64
64
|
set its own.
|
|
65
|
-
Each run gets a new run folder
|
|
65
|
+
Each run gets a new run folder. In `fill` mode it is named for the UTC time it was planned
|
|
66
|
+
(`runs/20261020-000429-brave-fox`). In `adhoc` mode each `run` command gets one folder named
|
|
67
|
+
that way, and its runs nest inside it (`runs/20261020-000429-brave-fox/quiet-river`); the run id
|
|
68
|
+
is that path under `runs/`. Either way, folder listings sort by start. `command` runs in a shell from the root, with
|
|
66
69
|
`{run_dir}` replaced by the run folder's absolute path. Pass it to the
|
|
67
70
|
script so it writes its outputs there.
|
|
68
71
|
Each param is appended as `--name value`, with the name as written (use
|
|
69
72
|
`source-language` for a `--source-language` flag), booleans as 0 or 1, and lists
|
|
70
73
|
and tables as JSON. labnotes writes `run.json` (params, created, ended, exit
|
|
71
|
-
code,
|
|
72
|
-
query it as `run ->> 'invocation_id'`), `stdout.log` and `stderr.log` to the
|
|
74
|
+
code), `stdout.log` and `stderr.log` to the
|
|
73
75
|
run folder.
|
|
74
76
|
A run counts when it exited 0 and its `run.json` doesn't hold
|
|
75
77
|
`"excluded": true`, so excluding a run by hand means adding that key; the next
|
|
@@ -15,11 +15,9 @@ async def execute_run(
|
|
|
15
15
|
params: dict[str, object],
|
|
16
16
|
running: set[asyncio.subprocess.Process],
|
|
17
17
|
stopping: asyncio.Event,
|
|
18
|
-
invocation: str,
|
|
19
18
|
) -> dict[str, object]:
|
|
20
19
|
run: dict[str, object] = {
|
|
21
20
|
"params": params,
|
|
22
|
-
"invocation_id": invocation,
|
|
23
21
|
"created": iso_time(time.time()),
|
|
24
22
|
"ended": None,
|
|
25
23
|
"exit_code": None,
|
|
@@ -12,7 +12,7 @@ from labnotes.models import (
|
|
|
12
12
|
Report,
|
|
13
13
|
)
|
|
14
14
|
from labnotes.runner.execute_run import execute_run
|
|
15
|
-
from labnotes.runner.
|
|
15
|
+
from labnotes.runner.new_run_id import new_run_id
|
|
16
16
|
from labnotes.runner.new_stamped_run_id import new_stamped_run_id
|
|
17
17
|
from labnotes.runner.param_grid import param_grid
|
|
18
18
|
from labnotes.runner.plan_runs import plan_runs
|
|
@@ -44,12 +44,18 @@ async def run_experiment(
|
|
|
44
44
|
run in planned or run_folder(config.root, config.runs, name, run).exists()
|
|
45
45
|
)
|
|
46
46
|
|
|
47
|
-
|
|
47
|
+
group = new_stamped_run_id(taken) if experiment.mode == "adhoc" else None
|
|
48
|
+
|
|
49
|
+
def new_id() -> str:
|
|
50
|
+
if group is None:
|
|
51
|
+
return new_stamped_run_id(taken)
|
|
52
|
+
return f"{group}/" + new_run_id(lambda run: taken(f"{group}/{run}"))
|
|
53
|
+
|
|
48
54
|
plan: list[Entry] = []
|
|
49
55
|
for params in plan_runs(
|
|
50
56
|
param_grid(experiment.params), experiment.repeats, finished
|
|
51
57
|
):
|
|
52
|
-
run =
|
|
58
|
+
run = new_id()
|
|
53
59
|
planned.add(run)
|
|
54
60
|
plan.append((run, params))
|
|
55
61
|
runs = [{"run": run, "params": params} for run, params in plan]
|
|
@@ -131,7 +137,7 @@ async def run_experiment(
|
|
|
131
137
|
def retry(run: str) -> None:
|
|
132
138
|
if stopping.is_set() or run not in ended:
|
|
133
139
|
return
|
|
134
|
-
again =
|
|
140
|
+
again = new_id()
|
|
135
141
|
planned.add(again)
|
|
136
142
|
plan.append((again, ended[run]))
|
|
137
143
|
report({"event": "retried", "run": run, "retry": again, "params": ended[run]})
|
|
@@ -182,7 +188,6 @@ async def run_experiment(
|
|
|
182
188
|
params,
|
|
183
189
|
running[run],
|
|
184
190
|
stopping,
|
|
185
|
-
invocation,
|
|
186
191
|
)
|
|
187
192
|
del running[run]
|
|
188
193
|
report({"event": "ended", "run": run, **result})
|
|
@@ -6,6 +6,9 @@ TOKEN = re.compile(r"\{([^}]*)\}|\*\*|\*|\?|[^{*?]+")
|
|
|
6
6
|
def path_pattern(template: str) -> re.Pattern[str]:
|
|
7
7
|
parts = []
|
|
8
8
|
for found in TOKEN.finditer(template):
|
|
9
|
+
if found.group(1) == "run":
|
|
10
|
+
parts.append("(?P<run>[^/]+(?:/[^/]+)?)")
|
|
11
|
+
continue
|
|
9
12
|
if found.group(1) is not None:
|
|
10
13
|
parts.append(f"(?P<{found.group(1)}>[^/]+)")
|
|
11
14
|
continue
|
|
@@ -110,7 +110,7 @@ class RunApp(App[None]):
|
|
|
110
110
|
case "planned":
|
|
111
111
|
for planned in event["runs"]: # type: ignore[attr-defined]
|
|
112
112
|
args = " ".join(param_args(planned["params"]))
|
|
113
|
-
cells = (planned["run"], args, PENDING, "")
|
|
113
|
+
cells = (planned["run"].rpartition("/")[2], args, PENDING, "")
|
|
114
114
|
self.cells[planned["run"]] = [*map(dim, cells)]
|
|
115
115
|
self.pending.add(planned["run"])
|
|
116
116
|
self.redraw(table.cursor_row)
|
|
@@ -161,7 +161,9 @@ class RunApp(App[None]):
|
|
|
161
161
|
case "retried":
|
|
162
162
|
again = str(event["retry"])
|
|
163
163
|
args = " ".join(param_args(event["params"])) # type: ignore[arg-type]
|
|
164
|
-
self.cells[again] = [
|
|
164
|
+
self.cells[again] = [
|
|
165
|
+
*map(dim, (again.rpartition("/")[2], args, PENDING, ""))
|
|
166
|
+
]
|
|
165
167
|
self.pending.add(again)
|
|
166
168
|
self.redraw(table.cursor_row)
|
|
167
169
|
self.show(describe_event(event))
|
|
@@ -7,5 +7,11 @@ reason = "LOCK_EX and LOCK_NB are disjoint bits, so | equals + and ^"
|
|
|
7
7
|
[[python.exempt]]
|
|
8
8
|
path = "src/labnotes/tui/run_app.py"
|
|
9
9
|
rules = ["mutation"]
|
|
10
|
-
lines = [
|
|
10
|
+
lines = [311]
|
|
11
11
|
reason = "last_stream is always the same str object taken from STREAMS, so is not equals !="
|
|
12
|
+
|
|
13
|
+
[[python.exempt]]
|
|
14
|
+
path = "src/labnotes/runner/run_experiment.py"
|
|
15
|
+
rules = ["mutation"]
|
|
16
|
+
lines = [47]
|
|
17
|
+
reason = "mode is validated to fill or adhoc; \"fill\" <= \"adhoc\" is false like ==, and both are interned literals so is equals =="
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import json
|
|
3
|
+
import socket
|
|
4
|
+
import subprocess
|
|
5
|
+
import sys
|
|
6
|
+
import time
|
|
7
|
+
from collections.abc import Awaitable, Callable, Iterator
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
import pytest
|
|
11
|
+
from playwright.async_api import Page, async_playwright
|
|
12
|
+
|
|
13
|
+
CONFIG = (
|
|
14
|
+
'name = "Alpha study"\ncommand = "go"\nrepeats = 2\nmode = "fill"\n'
|
|
15
|
+
'[params]\nsource-language = ["en", "fr"]\n'
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def write(path: Path, text: str) -> None:
|
|
20
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
21
|
+
path.write_text(text, encoding="utf-8")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def run(project: Path, name: str, params: dict[str, object], **fields: object) -> None:
|
|
25
|
+
record = {"params": params, "created": f"2026-10-17T00:00:0{name[-1]}Z", **fields}
|
|
26
|
+
write(project / f"alpha/runs/{name}/run.json", json.dumps(record))
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def project(root: Path) -> Path:
|
|
30
|
+
write(root / "beta/files/notes.md", "beta notes")
|
|
31
|
+
write(root / ".labnotes.toml", "")
|
|
32
|
+
write(root / "alpha/experiment.toml", CONFIG)
|
|
33
|
+
write(root / "alpha/files/plan.md", "# The plan\n\n```python\n1 + 1\n```\n")
|
|
34
|
+
write(root / "alpha/files/results.md", "# Results\n\nAll good.\n")
|
|
35
|
+
run(root, "run1", {"source-language": "en"}, exit_code=0)
|
|
36
|
+
run(root, "run2", {"source-language": "fr"}, exit_code=1, stdout="boom")
|
|
37
|
+
return root
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def free_port() -> int:
|
|
41
|
+
with socket.socket() as sock:
|
|
42
|
+
sock.bind(("127.0.0.1", 0))
|
|
43
|
+
return int(sock.getsockname()[1])
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def serve(root: Path) -> Iterator[str]:
|
|
47
|
+
port = free_port()
|
|
48
|
+
labnotes = Path(sys.executable).parent / "labnotes"
|
|
49
|
+
process = subprocess.Popen(
|
|
50
|
+
[labnotes, "-c", root / ".labnotes.toml", "server", "-p", str(port)],
|
|
51
|
+
stdout=subprocess.DEVNULL,
|
|
52
|
+
stderr=subprocess.DEVNULL,
|
|
53
|
+
)
|
|
54
|
+
try:
|
|
55
|
+
for _ in range(100):
|
|
56
|
+
try:
|
|
57
|
+
socket.create_connection(("127.0.0.1", port)).close()
|
|
58
|
+
break
|
|
59
|
+
except OSError:
|
|
60
|
+
time.sleep(0.1)
|
|
61
|
+
yield f"http://127.0.0.1:{port}"
|
|
62
|
+
finally:
|
|
63
|
+
process.terminate()
|
|
64
|
+
process.wait()
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def browse(
|
|
68
|
+
url: str, check: Callable[[Page], Awaitable[None]], width: int = 1280
|
|
69
|
+
) -> None:
|
|
70
|
+
async def drive() -> None:
|
|
71
|
+
async with async_playwright() as playwright:
|
|
72
|
+
browser = await playwright.chromium.launch()
|
|
73
|
+
page = await browser.new_page(viewport={"width": width, "height": 800})
|
|
74
|
+
await page.goto(url)
|
|
75
|
+
await check(page)
|
|
76
|
+
await browser.close()
|
|
77
|
+
|
|
78
|
+
asyncio.run(drive())
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@pytest.fixture
|
|
82
|
+
def site(tmp_path: Path) -> Iterator[str]:
|
|
83
|
+
yield from serve(project(tmp_path))
|
|
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
|
|
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
|