lean-lsp-mcp 0.11.3__py3-none-any.whl → 0.12.1__py3-none-any.whl
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.
- lean_lsp_mcp/client_utils.py +0 -1
- lean_lsp_mcp/file_utils.py +5 -56
- lean_lsp_mcp/server.py +43 -19
- {lean_lsp_mcp-0.11.3.dist-info → lean_lsp_mcp-0.12.1.dist-info}/METADATA +3 -3
- lean_lsp_mcp-0.12.1.dist-info/RECORD +14 -0
- lean_lsp_mcp-0.11.3.dist-info/RECORD +0 -14
- {lean_lsp_mcp-0.11.3.dist-info → lean_lsp_mcp-0.12.1.dist-info}/WHEEL +0 -0
- {lean_lsp_mcp-0.11.3.dist-info → lean_lsp_mcp-0.12.1.dist-info}/entry_points.txt +0 -0
- {lean_lsp_mcp-0.11.3.dist-info → lean_lsp_mcp-0.12.1.dist-info}/licenses/LICENSE +0 -0
- {lean_lsp_mcp-0.11.3.dist-info → lean_lsp_mcp-0.12.1.dist-info}/top_level.txt +0 -0
lean_lsp_mcp/client_utils.py
CHANGED
|
@@ -34,7 +34,6 @@ def startup_client(ctx: Context):
|
|
|
34
34
|
return # Client already set up correctly - reuse it!
|
|
35
35
|
# Different project path - close old client
|
|
36
36
|
client.close()
|
|
37
|
-
ctx.request_context.lifespan_context.file_content_hashes.clear()
|
|
38
37
|
|
|
39
38
|
# Need to create a new client
|
|
40
39
|
with OutputCapture() as output:
|
lean_lsp_mcp/file_utils.py
CHANGED
|
@@ -1,13 +1,6 @@
|
|
|
1
|
-
from typing import Optional
|
|
1
|
+
from typing import Optional
|
|
2
2
|
from pathlib import Path
|
|
3
3
|
|
|
4
|
-
from mcp.server.fastmcp import Context
|
|
5
|
-
from mcp.server.fastmcp.utilities.logging import get_logger
|
|
6
|
-
from leanclient import LeanLSPClient
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
logger = get_logger(__name__)
|
|
10
|
-
|
|
11
4
|
|
|
12
5
|
def get_relative_file_path(lean_project_path: Path, file_path: str) -> Optional[str]:
|
|
13
6
|
"""Convert path relative to project path.
|
|
@@ -21,25 +14,24 @@ def get_relative_file_path(lean_project_path: Path, file_path: str) -> Optional[
|
|
|
21
14
|
"""
|
|
22
15
|
file_path_obj = Path(file_path)
|
|
23
16
|
|
|
24
|
-
#
|
|
17
|
+
# Absolute path under project
|
|
25
18
|
if file_path_obj.is_absolute() and file_path_obj.exists():
|
|
26
19
|
try:
|
|
27
20
|
return str(file_path_obj.relative_to(lean_project_path))
|
|
28
21
|
except ValueError:
|
|
29
|
-
# File is not in this project
|
|
30
22
|
return None
|
|
31
23
|
|
|
32
|
-
#
|
|
24
|
+
# Relative to project path
|
|
33
25
|
path = lean_project_path / file_path
|
|
34
26
|
if path.exists():
|
|
35
27
|
return str(path.relative_to(lean_project_path))
|
|
36
28
|
|
|
37
|
-
#
|
|
29
|
+
# Relative to CWD, but only if inside project root
|
|
38
30
|
cwd = Path.cwd()
|
|
39
31
|
path = cwd / file_path
|
|
40
32
|
if path.exists():
|
|
41
33
|
try:
|
|
42
|
-
return str(path.relative_to(lean_project_path))
|
|
34
|
+
return str(path.resolve().relative_to(lean_project_path))
|
|
43
35
|
except ValueError:
|
|
44
36
|
return None
|
|
45
37
|
|
|
@@ -55,46 +47,3 @@ def get_file_contents(abs_path: str) -> str:
|
|
|
55
47
|
continue
|
|
56
48
|
with open(abs_path, "r", encoding=None) as f:
|
|
57
49
|
return f.read()
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
def update_file(ctx: Context, rel_path: str) -> str:
|
|
61
|
-
"""Update the file contents in the context.
|
|
62
|
-
Args:
|
|
63
|
-
ctx (Context): Context object.
|
|
64
|
-
rel_path (str): Relative file path.
|
|
65
|
-
|
|
66
|
-
Returns:
|
|
67
|
-
str: Updated file contents.
|
|
68
|
-
"""
|
|
69
|
-
# Get file contents and hash
|
|
70
|
-
abs_path = ctx.request_context.lifespan_context.lean_project_path / rel_path
|
|
71
|
-
file_content = get_file_contents(str(abs_path))
|
|
72
|
-
hashed_file = hash(file_content)
|
|
73
|
-
|
|
74
|
-
# Check if file_contents have changed
|
|
75
|
-
file_content_hashes: Dict[str, str] = (
|
|
76
|
-
ctx.request_context.lifespan_context.file_content_hashes
|
|
77
|
-
)
|
|
78
|
-
if rel_path not in file_content_hashes:
|
|
79
|
-
file_content_hashes[rel_path] = hashed_file
|
|
80
|
-
return file_content
|
|
81
|
-
|
|
82
|
-
elif hashed_file == file_content_hashes[rel_path]:
|
|
83
|
-
return file_content
|
|
84
|
-
|
|
85
|
-
# Update file_contents
|
|
86
|
-
file_content_hashes[rel_path] = hashed_file
|
|
87
|
-
|
|
88
|
-
# Reload file in LSP
|
|
89
|
-
client: LeanLSPClient = ctx.request_context.lifespan_context.client
|
|
90
|
-
try:
|
|
91
|
-
client.close_files([rel_path])
|
|
92
|
-
except FileNotFoundError as e:
|
|
93
|
-
logger.warning(
|
|
94
|
-
f"Attempted to close file {rel_path} that wasn't open in LSP client: {e}"
|
|
95
|
-
)
|
|
96
|
-
except Exception as e:
|
|
97
|
-
logger.error(
|
|
98
|
-
f"Unexpected error closing file {rel_path}: {type(e).__name__}: {e}"
|
|
99
|
-
)
|
|
100
|
-
return file_content
|
lean_lsp_mcp/server.py
CHANGED
|
@@ -23,7 +23,7 @@ from lean_lsp_mcp.client_utils import (
|
|
|
23
23
|
startup_client,
|
|
24
24
|
infer_project_path,
|
|
25
25
|
)
|
|
26
|
-
from lean_lsp_mcp.file_utils import get_file_contents
|
|
26
|
+
from lean_lsp_mcp.file_utils import get_file_contents
|
|
27
27
|
from lean_lsp_mcp.instructions import INSTRUCTIONS
|
|
28
28
|
from lean_lsp_mcp.search_utils import check_ripgrep_status, lean_local_search
|
|
29
29
|
from lean_lsp_mcp.utils import (
|
|
@@ -51,7 +51,6 @@ _RG_AVAILABLE, _RG_MESSAGE = check_ripgrep_status()
|
|
|
51
51
|
class AppContext:
|
|
52
52
|
lean_project_path: Path | None
|
|
53
53
|
client: LeanLSPClient | None
|
|
54
|
-
file_content_hashes: Dict[str, str]
|
|
55
54
|
rate_limit: Dict[str, List[int]]
|
|
56
55
|
lean_search_available: bool
|
|
57
56
|
|
|
@@ -68,7 +67,6 @@ async def app_lifespan(server: FastMCP) -> AsyncIterator[AppContext]:
|
|
|
68
67
|
context = AppContext(
|
|
69
68
|
lean_project_path=lean_project_path,
|
|
70
69
|
client=None,
|
|
71
|
-
file_content_hashes={},
|
|
72
70
|
rate_limit={
|
|
73
71
|
"leansearch": [],
|
|
74
72
|
"loogle": [],
|
|
@@ -166,7 +164,6 @@ async def lsp_build(
|
|
|
166
164
|
if client:
|
|
167
165
|
ctx.request_context.lifespan_context.client = None
|
|
168
166
|
client.close()
|
|
169
|
-
ctx.request_context.lifespan_context.file_content_hashes.clear()
|
|
170
167
|
|
|
171
168
|
if clean:
|
|
172
169
|
subprocess.run(["lake", "clean"], cwd=lean_project_path_obj, check=False)
|
|
@@ -271,13 +268,20 @@ def file_contents(ctx: Context, file_path: str, annotate_lines: bool = True) ->
|
|
|
271
268
|
|
|
272
269
|
|
|
273
270
|
@mcp.tool("lean_diagnostic_messages")
|
|
274
|
-
def diagnostic_messages(
|
|
271
|
+
def diagnostic_messages(
|
|
272
|
+
ctx: Context,
|
|
273
|
+
file_path: str,
|
|
274
|
+
start_line: Optional[int] = None,
|
|
275
|
+
end_line: Optional[int] = None,
|
|
276
|
+
) -> List[str] | str:
|
|
275
277
|
"""Get all diagnostic msgs (errors, warnings, infos) for a Lean file.
|
|
276
278
|
|
|
277
279
|
"no goals to be solved" means code may need removal.
|
|
278
280
|
|
|
279
281
|
Args:
|
|
280
282
|
file_path (str): Abs path to Lean file
|
|
283
|
+
start_line (int, optional): Start line (1-indexed). Filters from this line.
|
|
284
|
+
end_line (int, optional): End line (1-indexed). Filters to this line.
|
|
281
285
|
|
|
282
286
|
Returns:
|
|
283
287
|
List[str] | str: Diagnostic msgs or error msg
|
|
@@ -286,10 +290,19 @@ def diagnostic_messages(ctx: Context, file_path: str) -> List[str] | str:
|
|
|
286
290
|
if not rel_path:
|
|
287
291
|
return "Invalid Lean file path: Unable to start LSP server or load file"
|
|
288
292
|
|
|
289
|
-
update_file(ctx, rel_path)
|
|
290
|
-
|
|
291
293
|
client: LeanLSPClient = ctx.request_context.lifespan_context.client
|
|
292
|
-
|
|
294
|
+
|
|
295
|
+
# Convert 1-indexed to 0-indexed for leanclient
|
|
296
|
+
start_line_0 = (start_line - 1) if start_line is not None else None
|
|
297
|
+
end_line_0 = (end_line - 1) if end_line is not None else None
|
|
298
|
+
|
|
299
|
+
diagnostics = client.get_diagnostics(
|
|
300
|
+
rel_path,
|
|
301
|
+
start_line=start_line_0,
|
|
302
|
+
end_line=end_line_0,
|
|
303
|
+
inactivity_timeout=8.0,
|
|
304
|
+
)
|
|
305
|
+
|
|
293
306
|
return format_diagnostics(diagnostics)
|
|
294
307
|
|
|
295
308
|
|
|
@@ -314,8 +327,9 @@ def goal(ctx: Context, file_path: str, line: int, column: Optional[int] = None)
|
|
|
314
327
|
if not rel_path:
|
|
315
328
|
return "Invalid Lean file path: Unable to start LSP server or load file"
|
|
316
329
|
|
|
317
|
-
content = update_file(ctx, rel_path)
|
|
318
330
|
client: LeanLSPClient = ctx.request_context.lifespan_context.client
|
|
331
|
+
client.open_file(rel_path)
|
|
332
|
+
content = client.get_file_content(rel_path)
|
|
319
333
|
|
|
320
334
|
if column is None:
|
|
321
335
|
lines = content.splitlines()
|
|
@@ -360,14 +374,15 @@ def term_goal(
|
|
|
360
374
|
if not rel_path:
|
|
361
375
|
return "Invalid Lean file path: Unable to start LSP server or load file"
|
|
362
376
|
|
|
363
|
-
|
|
377
|
+
client: LeanLSPClient = ctx.request_context.lifespan_context.client
|
|
378
|
+
client.open_file(rel_path)
|
|
379
|
+
content = client.get_file_content(rel_path)
|
|
364
380
|
if column is None:
|
|
365
381
|
lines = content.splitlines()
|
|
366
382
|
if line < 1 or line > len(lines):
|
|
367
383
|
return "Line number out of range. Try elsewhere?"
|
|
368
384
|
column = len(content.splitlines()[line - 1])
|
|
369
385
|
|
|
370
|
-
client: LeanLSPClient = ctx.request_context.lifespan_context.client
|
|
371
386
|
term_goal = client.get_term_goal(rel_path, line - 1, column - 1)
|
|
372
387
|
f_line = format_line(content, line, column)
|
|
373
388
|
if term_goal is None:
|
|
@@ -394,8 +409,9 @@ def hover(ctx: Context, file_path: str, line: int, column: int) -> str:
|
|
|
394
409
|
if not rel_path:
|
|
395
410
|
return "Invalid Lean file path: Unable to start LSP server or load file"
|
|
396
411
|
|
|
397
|
-
file_content = update_file(ctx, rel_path)
|
|
398
412
|
client: LeanLSPClient = ctx.request_context.lifespan_context.client
|
|
413
|
+
client.open_file(rel_path)
|
|
414
|
+
file_content = client.get_file_content(rel_path)
|
|
399
415
|
hover_info = client.get_hover(rel_path, line - 1, column - 1)
|
|
400
416
|
if hover_info is None:
|
|
401
417
|
f_line = format_line(file_content, line, column)
|
|
@@ -440,9 +456,10 @@ def completions(
|
|
|
440
456
|
rel_path = setup_client_for_file(ctx, file_path)
|
|
441
457
|
if not rel_path:
|
|
442
458
|
return "Invalid Lean file path: Unable to start LSP server or load file"
|
|
443
|
-
content = update_file(ctx, rel_path)
|
|
444
459
|
|
|
445
460
|
client: LeanLSPClient = ctx.request_context.lifespan_context.client
|
|
461
|
+
client.open_file(rel_path)
|
|
462
|
+
content = client.get_file_content(rel_path)
|
|
446
463
|
completions = client.get_completions(rel_path, line - 1, column - 1)
|
|
447
464
|
formatted = [c["label"] for c in completions if "label" in c]
|
|
448
465
|
f_line = format_line(content, line, column)
|
|
@@ -502,14 +519,16 @@ def declaration_file(ctx: Context, file_path: str, symbol: str) -> str:
|
|
|
502
519
|
rel_path = setup_client_for_file(ctx, file_path)
|
|
503
520
|
if not rel_path:
|
|
504
521
|
return "Invalid Lean file path: Unable to start LSP server or load file"
|
|
505
|
-
|
|
522
|
+
|
|
523
|
+
client: LeanLSPClient = ctx.request_context.lifespan_context.client
|
|
524
|
+
client.open_file(rel_path)
|
|
525
|
+
orig_file_content = client.get_file_content(rel_path)
|
|
506
526
|
|
|
507
527
|
# Find the first occurence of the symbol (line and column) in the file,
|
|
508
528
|
position = find_start_position(orig_file_content, symbol)
|
|
509
529
|
if not position:
|
|
510
530
|
return f"Symbol `{symbol}` (case sensitive) not found in file `{rel_path}`. Add it first, then try again."
|
|
511
531
|
|
|
512
|
-
client: LeanLSPClient = ctx.request_context.lifespan_context.client
|
|
513
532
|
declaration = client.get_declarations(
|
|
514
533
|
rel_path, position["line"], position["column"]
|
|
515
534
|
)
|
|
@@ -557,8 +576,9 @@ def multi_attempt(
|
|
|
557
576
|
rel_path = setup_client_for_file(ctx, file_path)
|
|
558
577
|
if not rel_path:
|
|
559
578
|
return "Invalid Lean file path: Unable to start LSP server or load file"
|
|
560
|
-
|
|
579
|
+
|
|
561
580
|
client: LeanLSPClient = ctx.request_context.lifespan_context.client
|
|
581
|
+
client.open_file(rel_path)
|
|
562
582
|
|
|
563
583
|
try:
|
|
564
584
|
client.open_file(rel_path)
|
|
@@ -637,7 +657,9 @@ def run_code(ctx: Context, code: str) -> List[str] | str:
|
|
|
637
657
|
assert client is not None # startup_client guarantees an initialized client
|
|
638
658
|
client.open_file(rel_path)
|
|
639
659
|
opened_file = True
|
|
640
|
-
diagnostics = format_diagnostics(
|
|
660
|
+
diagnostics = format_diagnostics(
|
|
661
|
+
client.get_diagnostics(rel_path, inactivity_timeout=8.0)
|
|
662
|
+
)
|
|
641
663
|
finally:
|
|
642
664
|
if opened_file:
|
|
643
665
|
try:
|
|
@@ -858,8 +880,9 @@ def state_search(
|
|
|
858
880
|
if not rel_path:
|
|
859
881
|
return "Invalid Lean file path: Unable to start LSP server or load file"
|
|
860
882
|
|
|
861
|
-
file_contents = update_file(ctx, rel_path)
|
|
862
883
|
client: LeanLSPClient = ctx.request_context.lifespan_context.client
|
|
884
|
+
client.open_file(rel_path)
|
|
885
|
+
file_contents = client.get_file_content(rel_path)
|
|
863
886
|
goal = client.get_goal(rel_path, line - 1, column - 1)
|
|
864
887
|
|
|
865
888
|
f_line = format_line(file_contents, line, column)
|
|
@@ -908,8 +931,9 @@ def hammer_premise(
|
|
|
908
931
|
if not rel_path:
|
|
909
932
|
return "Invalid Lean file path: Unable to start LSP server or load file"
|
|
910
933
|
|
|
911
|
-
file_contents = update_file(ctx, rel_path)
|
|
912
934
|
client: LeanLSPClient = ctx.request_context.lifespan_context.client
|
|
935
|
+
client.open_file(rel_path)
|
|
936
|
+
file_contents = client.get_file_content(rel_path)
|
|
913
937
|
goal = client.get_goal(rel_path, line - 1, column - 1)
|
|
914
938
|
|
|
915
939
|
f_line = format_line(file_contents, line, column)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lean-lsp-mcp
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.12.1
|
|
4
4
|
Summary: Lean Theorem Prover MCP
|
|
5
5
|
Author-email: Oliver Dressler <hey@oli.show>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -8,8 +8,8 @@ Project-URL: Repository, https://github.com/oOo0oOo/lean-lsp-mcp
|
|
|
8
8
|
Requires-Python: >=3.10
|
|
9
9
|
Description-Content-Type: text/markdown
|
|
10
10
|
License-File: LICENSE
|
|
11
|
-
Requires-Dist: leanclient==0.
|
|
12
|
-
Requires-Dist: mcp[cli]==1.
|
|
11
|
+
Requires-Dist: leanclient==0.5.1
|
|
12
|
+
Requires-Dist: mcp[cli]==1.21.0
|
|
13
13
|
Requires-Dist: orjson>=3.11.1
|
|
14
14
|
Provides-Extra: lint
|
|
15
15
|
Requires-Dist: ruff>=0.2.0; extra == "lint"
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
lean_lsp_mcp/__init__.py,sha256=lxqDq0G_sI2iu2Nniy-pTW7BE9Ux7ZXeDoGf0OAWIDc,763
|
|
2
|
+
lean_lsp_mcp/__main__.py,sha256=XnpTzfJc0T-j9tHtdkA8ovTr1c139ffTewcJGhxYDaM,49
|
|
3
|
+
lean_lsp_mcp/client_utils.py,sha256=hF941DEeRE3ICMgMhv9J4vv6bO4hZPJOAcFU03yIDXs,4988
|
|
4
|
+
lean_lsp_mcp/file_utils.py,sha256=kCTYQSfmV-R2cm_NCi_L8W5Dcsm0_rTOPpTtpyAin78,1365
|
|
5
|
+
lean_lsp_mcp/instructions.py,sha256=y_gHlbeJoKnPohmcSVrQQds6mbBO1en-lxnXAfEypZE,892
|
|
6
|
+
lean_lsp_mcp/search_utils.py,sha256=X2LPynDNLi767UDxbxHpMccOkbnfKJKv_HxvRNxIXM4,3984
|
|
7
|
+
lean_lsp_mcp/server.py,sha256=AiPu6yw_m1wQdooXyhgKUGuL-Kmin4GNoz79iZNnTps,35457
|
|
8
|
+
lean_lsp_mcp/utils.py,sha256=zLu2VIhaX4yocY07F3Z94LB2jRGrkH1ID9SjR3poE9A,8255
|
|
9
|
+
lean_lsp_mcp-0.12.1.dist-info/licenses/LICENSE,sha256=CQlxnf0tQyoVrBE93JYvAUYxv6Z5Yg6sX0pwogOkFvo,1071
|
|
10
|
+
lean_lsp_mcp-0.12.1.dist-info/METADATA,sha256=fwr43eEL0JDzu2wv9JJPoInsJyRL1dlGcz8RUhpPXs8,19626
|
|
11
|
+
lean_lsp_mcp-0.12.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
12
|
+
lean_lsp_mcp-0.12.1.dist-info/entry_points.txt,sha256=nQbvwctWkWD7I-2f4VrdVQBZYGUw8CnUnFC6QjXxOSE,51
|
|
13
|
+
lean_lsp_mcp-0.12.1.dist-info/top_level.txt,sha256=LGEK0lgMSNPIQ6mG8EO-adaZEGPi_0daDs004epOTF0,13
|
|
14
|
+
lean_lsp_mcp-0.12.1.dist-info/RECORD,,
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
lean_lsp_mcp/__init__.py,sha256=lxqDq0G_sI2iu2Nniy-pTW7BE9Ux7ZXeDoGf0OAWIDc,763
|
|
2
|
-
lean_lsp_mcp/__main__.py,sha256=XnpTzfJc0T-j9tHtdkA8ovTr1c139ffTewcJGhxYDaM,49
|
|
3
|
-
lean_lsp_mcp/client_utils.py,sha256=FNfiEWQagRj9tmo2xUDxbSZYVz5_RfaNH6OApgNQ9ZM,5065
|
|
4
|
-
lean_lsp_mcp/file_utils.py,sha256=qddegF-T5-egZop8dPe_3Cma-3rRSKsAErVDQLecmbE,2916
|
|
5
|
-
lean_lsp_mcp/instructions.py,sha256=y_gHlbeJoKnPohmcSVrQQds6mbBO1en-lxnXAfEypZE,892
|
|
6
|
-
lean_lsp_mcp/search_utils.py,sha256=X2LPynDNLi767UDxbxHpMccOkbnfKJKv_HxvRNxIXM4,3984
|
|
7
|
-
lean_lsp_mcp/server.py,sha256=zwiHeMlgRpERNIF-E_6OKzI8a-YmAx09EVX3QO2gJAk,34792
|
|
8
|
-
lean_lsp_mcp/utils.py,sha256=zLu2VIhaX4yocY07F3Z94LB2jRGrkH1ID9SjR3poE9A,8255
|
|
9
|
-
lean_lsp_mcp-0.11.3.dist-info/licenses/LICENSE,sha256=CQlxnf0tQyoVrBE93JYvAUYxv6Z5Yg6sX0pwogOkFvo,1071
|
|
10
|
-
lean_lsp_mcp-0.11.3.dist-info/METADATA,sha256=1mf98hyaVVN1lZLWMZEdtSV6-nDZ7KAFyVLPRc7gbx8,19626
|
|
11
|
-
lean_lsp_mcp-0.11.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
12
|
-
lean_lsp_mcp-0.11.3.dist-info/entry_points.txt,sha256=nQbvwctWkWD7I-2f4VrdVQBZYGUw8CnUnFC6QjXxOSE,51
|
|
13
|
-
lean_lsp_mcp-0.11.3.dist-info/top_level.txt,sha256=LGEK0lgMSNPIQ6mG8EO-adaZEGPi_0daDs004epOTF0,13
|
|
14
|
-
lean_lsp_mcp-0.11.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|