Skip to content

Commit 3e9b795

Browse files
committed
improve warning handling and docs string
1 parent 31caef2 commit 3e9b795

File tree

7 files changed

+153
-9
lines changed

7 files changed

+153
-9
lines changed

src/sphinx_codelinks/analyse/analyse.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import json
44
import logging
55
from pathlib import Path
6-
from typing import Any, TypedDict
6+
from typing import Any, TypedDict, cast
77

88
from lark import UnexpectedInput
99
from tree_sitter import Node as TreeSitterNode
@@ -56,6 +56,8 @@ class AnalyseWarning:
5656

5757

5858
class SourceAnalyse:
59+
"""Analyse source files from a single project."""
60+
5961
def __init__(
6062
self,
6163
analyse_config: SourceAnalyseConfig,
@@ -427,6 +429,11 @@ def extract_marked_content(self) -> None:
427429
logger.info(f"Oneline needs extracted: {len(self.oneline_needs)}")
428430
if self.analyse_config.get_rst:
429431
logger.info(f"Marked rst extracted: {len(self.marked_rst)}")
432+
cnt_resolved = 0
433+
for rst in self.marked_rst:
434+
if rst.need:
435+
cnt_resolved += 1
436+
logger.info(f"Marked rst valid to need: {cnt_resolved}")
430437

431438
def merge_marked_content(self) -> None:
432439
self.all_marked_content.extend(self.need_id_refs)
@@ -438,6 +445,10 @@ def merge_marked_content(self) -> None:
438445
)
439446

440447
def dump_marked_content(self, outdir: Path) -> None:
448+
"""Dump marked content to the given output directory.
449+
450+
This function is mainly for API users who want to dump marked content separately.
451+
"""
441452
output_path = outdir / "marked_content.json"
442453
if not output_path.parent.exists():
443454
output_path.parent.mkdir(parents=True)
@@ -448,6 +459,25 @@ def dump_marked_content(self, outdir: Path) -> None:
448459
json.dump(to_dump, f)
449460
logger.info(f"Marked content dumped to {output_path}")
450461

462+
def dump_warnings(self, outdir: Path) -> None:
463+
"""Dump warnings to the given output directory.
464+
465+
This function is mainly for API users who want to dump warnings separately.
466+
"""
467+
output_path = outdir / "analyse_warnings.json"
468+
if not output_path.parent.exists():
469+
output_path.parent.mkdir(parents=True)
470+
current_warnings: list[AnalyseWarningType] = [
471+
cast(AnalyseWarningType, _warning.__dict__)
472+
for _warning in list(self.rst_warnings) + list(self.oneline_warnings)
473+
]
474+
with output_path.open("w") as f:
475+
json.dump(
476+
current_warnings,
477+
f,
478+
)
479+
logger.info(f"Warnings dumped to {output_path}")
480+
451481
def run(self) -> None:
452482
self.create_src_objects()
453483
self.extract_marked_content()

src/sphinx_codelinks/analyse/projects.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@
2020

2121

2222
class AnalyseProjects:
23+
"""Analyse multiple projects based on the given CodeLinksConfig.
24+
25+
This class uses SourceAnalyse for each project defined in the CodeLinksConfig.
26+
"""
27+
2328
warning_filepath: Path = Path("warnings") / "codelinks_warnings.json"
2429

2530
def __init__(self, codelink_config: CodeLinksConfig) -> None:
@@ -67,18 +72,25 @@ def load_warnings(cls, warnings_dir: Path) -> list[AnalyseWarning] | None:
6772
return loaded_warnings
6873

6974
def update_warnings(self) -> None:
75+
"""Update and dump warnings from all projects' analyses."""
7076
current_warnings: list[AnalyseWarningType] = [
7177
cast(AnalyseWarningType, _warning.__dict__)
7278
for analyse in self.projects_analyse.values()
73-
for _warning in analyse.oneline_warnings
79+
for _warning in list(analyse.rst_warnings) + list(analyse.oneline_warnings)
7480
]
81+
82+
if not current_warnings:
83+
logger.info("No warnings to dump.")
84+
return
7585
self.dump_warnings(current_warnings)
7686

7787
def dump_warnings(self, warnings: list[AnalyseWarningType]) -> None:
88+
"""Dump warnings to the configured warnings path."""
7889
if not self.warnings_path.parent.exists():
7990
self.warnings_path.parent.mkdir(parents=True)
8091
with self.warnings_path.open("w") as f:
8192
json.dump(
8293
warnings,
8394
f,
8495
)
96+
logger.info(f"Warnings dumped to {self.warnings_path}")

src/sphinx_codelinks/cmd.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ def analyse(
151151
analyse_projects = AnalyseProjects(codelinks_config)
152152
analyse_projects.run()
153153
analyse_projects.dump_markers()
154+
analyse_projects.update_warnings()
154155

155156

156157
@app.command(no_args_is_help=True)
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
[
2+
{
3+
"filepath": "dummy_1.c",
4+
"remote_url": null,
5+
"source_map": {
6+
"start": {
7+
"row": 2,
8+
"column": 0
9+
},
10+
"end": {
11+
"row": 6,
12+
"column": 59
13+
}
14+
},
15+
"tagged_scope": "int main() {\n return 0;\n}",
16+
"rst": " .. imp@:: implement main function\n :id: REQ_001\n :links: IMPL_001, IMPL_002\n\n This is content for the main function implementation.\n ",
17+
"need": null,
18+
"type": "rst"
19+
},
20+
{
21+
"filepath": "dummy_1.c",
22+
"remote_url": null,
23+
"source_map": {
24+
"start": {
25+
"row": 14,
26+
"column": 0
27+
},
28+
"end": {
29+
"row": 18,
30+
"column": 59
31+
}
32+
},
33+
"tagged_scope": "int func1() {\n return 0;\n}",
34+
"rst": " .. impl:: implement main function\n :id: REQ_002\n :links: IMPL_001, IMPL_002\n\n This is content for the main function implementation.\n ",
35+
"need": {
36+
"type": "impl",
37+
"title": "implement main function",
38+
"content": "This is content for the main function implementation.",
39+
"id": "REQ_002",
40+
"links": [
41+
"IMPL_001",
42+
" IMPL_002"
43+
]
44+
},
45+
"type": "rst"
46+
}
47+
]

tests/conftest.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,9 +99,9 @@ def serialize(self, data, **_kwargs):
9999

100100
@pytest.fixture
101101
def snapshot_marks(snapshot):
102-
"""Snapshot fixture for reqif.
102+
"""Snapshot fixture for markers.
103103
104-
Sanitize the reqif, to make the snapshots reproducible.
104+
Sanitize the markers, to make the snapshots reproducible.
105105
"""
106106
return snapshot.with_defaults(extension_class=AnchorsSnapshotExtension)
107107

tests/fixture_files/analyse_rst.yml

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,3 +67,46 @@ link_options_rst_marker:
6767
int main() {
6868
return 0;
6969
}
70+
71+
link_options_rst_marker:
72+
dummy_1.c: |
73+
/*
74+
* @rst
75+
* .. impl:: implement main function
76+
* :id: REQ_001
77+
* :links: IMPL_001, IMPL_002
78+
*
79+
* This is content for the main function implementation.
80+
* @endrst
81+
*/
82+
int main() {
83+
return 0;
84+
}
85+
86+
warning_invalid_type:
87+
dummy_1.c: |
88+
/*
89+
* @rst
90+
* .. imp@:: implement main function
91+
* :id: REQ_001
92+
* :links: IMPL_001, IMPL_002
93+
*
94+
* This is content for the main function implementation.
95+
* @endrst
96+
*/
97+
int main() {
98+
return 0;
99+
}
100+
/*
101+
* @rst
102+
* .. impl:: implement main function
103+
* :id: REQ_002
104+
* :links: IMPL_001, IMPL_002
105+
*
106+
* This is content for the main function implementation.
107+
* @endrst
108+
*/
109+
int func1() {
110+
return 0;
111+
}
112+
warnings_cnt: 1

tests/test_analyse.py

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -161,14 +161,25 @@ def test_analyse_rst(
161161
src_analyse.dump_marked_content(tmp_path)
162162
dumped_content = tmp_path / "marked_content.json"
163163

164-
# assert src_analyse.rst_warnings
165164
assert dumped_content.exists()
166165

167166
with dumped_content.open("r") as f:
168167
marked_content = json.load(f)
169168
# normalize filepath
170-
for obj in marked_content:
171-
obj["filepath"] = (
172-
Path(obj["filepath"]).relative_to(src_analyse_config.src_dir)
173-
).as_posix()
169+
normalize_file_path(marked_content, src_analyse_config.src_dir)
174170
assert marked_content == snapshot_marks
171+
172+
if "warnings_cnt" in content:
173+
assert len(src_analyse.rst_warnings) == content["warnings_cnt"]
174+
src_analyse.dump_warnings(tmp_path)
175+
dumped_warnings = tmp_path / "analyse_warnings.json"
176+
assert dumped_warnings.exists()
177+
178+
179+
def normalize_file_path(analysed_content: list[dict[str, Any]], src_dir: Path) -> None:
180+
"""Normalize the file paths in the analysed content to be relative to src_dir.
181+
182+
It is for the test results to be consistent across different environments.
183+
"""
184+
for obj in analysed_content:
185+
obj["filepath"] = (Path(obj["filepath"]).relative_to(src_dir)).as_posix()

0 commit comments

Comments
 (0)