Skip to content

Commit 01029db

Browse files
committed
improve warning handling and docs string
1 parent 97e0e9b commit 01029db

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,
@@ -430,6 +432,11 @@ def extract_marked_content(self) -> None:
430432
logger.info(f"Oneline needs extracted: {len(self.oneline_needs)}")
431433
if self.analyse_config.get_rst:
432434
logger.info(f"Marked rst extracted: {len(self.marked_rst)}")
435+
cnt_resolved = 0
436+
for rst in self.marked_rst:
437+
if rst.need:
438+
cnt_resolved += 1
439+
logger.info(f"Marked rst valid to need: {cnt_resolved}")
433440

434441
def merge_marked_content(self) -> None:
435442
self.all_marked_content.extend(self.need_id_refs)
@@ -441,6 +448,10 @@ def merge_marked_content(self) -> None:
441448
)
442449

443450
def dump_marked_content(self, outdir: Path) -> None:
451+
"""Dump marked content to the given output directory.
452+
453+
This function is mainly for API users who want to dump marked content separately.
454+
"""
444455
output_path = outdir / "marked_content.json"
445456
if not output_path.parent.exists():
446457
output_path.parent.mkdir(parents=True)
@@ -451,6 +462,25 @@ def dump_marked_content(self, outdir: Path) -> None:
451462
json.dump(to_dump, f)
452463
logger.info(f"Marked content dumped to {output_path}")
453464

465+
def dump_warnings(self, outdir: Path) -> None:
466+
"""Dump warnings to the given output directory.
467+
468+
This function is mainly for API users who want to dump warnings separately.
469+
"""
470+
output_path = outdir / "analyse_warnings.json"
471+
if not output_path.parent.exists():
472+
output_path.parent.mkdir(parents=True)
473+
current_warnings: list[AnalyseWarningType] = [
474+
cast(AnalyseWarningType, _warning.__dict__)
475+
for _warning in list(self.rst_warnings) + list(self.oneline_warnings)
476+
]
477+
with output_path.open("w") as f:
478+
json.dump(
479+
current_warnings,
480+
f,
481+
)
482+
logger.info(f"Warnings dumped to {output_path}")
483+
454484
def run(self) -> None:
455485
self.create_src_objects()
456486
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
@@ -152,6 +152,7 @@ def analyse(
152152
analyse_projects = AnalyseProjects(codelinks_config)
153153
analyse_projects.run()
154154
analyse_projects.dump_markers()
155+
analyse_projects.update_warnings()
155156

156157

157158
@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
@@ -162,14 +162,25 @@ def test_analyse_rst(
162162
src_analyse.dump_marked_content(tmp_path)
163163
dumped_content = tmp_path / "marked_content.json"
164164

165-
# assert src_analyse.rst_warnings
166165
assert dumped_content.exists()
167166

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

0 commit comments

Comments
 (0)