sphinx-codelinks 0.1.1__tar.gz → 0.1.2__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.
Files changed (19) hide show
  1. {sphinx_codelinks-0.1.1 → sphinx_codelinks-0.1.2}/PKG-INFO +1 -1
  2. {sphinx_codelinks-0.1.1 → sphinx_codelinks-0.1.2}/pyproject.toml +1 -1
  3. {sphinx_codelinks-0.1.1 → sphinx_codelinks-0.1.2}/src/sphinx_codelinks/cmd.py +8 -8
  4. {sphinx_codelinks-0.1.1 → sphinx_codelinks-0.1.2}/src/sphinx_codelinks/source_discovery/config.py +6 -4
  5. {sphinx_codelinks-0.1.1 → sphinx_codelinks-0.1.2}/src/sphinx_codelinks/source_discovery/source_discover.py +2 -2
  6. {sphinx_codelinks-0.1.1 → sphinx_codelinks-0.1.2}/src/sphinx_codelinks/sphinx_extension/config.py +37 -20
  7. {sphinx_codelinks-0.1.1 → sphinx_codelinks-0.1.2}/src/sphinx_codelinks/sphinx_extension/directives/src_trace.py +44 -23
  8. {sphinx_codelinks-0.1.1 → sphinx_codelinks-0.1.2}/src/sphinx_codelinks/sphinx_extension/source_tracing.py +7 -0
  9. {sphinx_codelinks-0.1.1 → sphinx_codelinks-0.1.2}/LICENSE +0 -0
  10. {sphinx_codelinks-0.1.1 → sphinx_codelinks-0.1.2}/README.md +0 -0
  11. {sphinx_codelinks-0.1.1 → sphinx_codelinks-0.1.2}/src/sphinx_codelinks/README.md +0 -0
  12. {sphinx_codelinks-0.1.1 → sphinx_codelinks-0.1.2}/src/sphinx_codelinks/__init__.py +0 -0
  13. {sphinx_codelinks-0.1.1 → sphinx_codelinks-0.1.2}/src/sphinx_codelinks/sphinx_extension/debug.py +0 -0
  14. {sphinx_codelinks-0.1.1 → sphinx_codelinks-0.1.2}/src/sphinx_codelinks/sphinx_extension/html_wrapper.py +0 -0
  15. {sphinx_codelinks-0.1.1 → sphinx_codelinks-0.1.2}/src/sphinx_codelinks/sphinx_extension/ub_sct.css +0 -0
  16. {sphinx_codelinks-0.1.1 → sphinx_codelinks-0.1.2}/src/sphinx_codelinks/virtual_docs/config.py +0 -0
  17. {sphinx_codelinks-0.1.1 → sphinx_codelinks-0.1.2}/src/sphinx_codelinks/virtual_docs/ubt_models.py +0 -0
  18. {sphinx_codelinks-0.1.1 → sphinx_codelinks-0.1.2}/src/sphinx_codelinks/virtual_docs/utils.py +0 -0
  19. {sphinx_codelinks-0.1.1 → sphinx_codelinks-0.1.2}/src/sphinx_codelinks/virtual_docs/virtual_docs.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sphinx-codelinks
3
- Version: 0.1.1
3
+ Version: 0.1.2
4
4
  Summary: Fast Source Code Traceability for Sphinx-Needs
5
5
  Author-email: team useblocks <info@useblocks.com>
6
6
  Maintainer-email: Marco Heinemann <marco.heinemann@useblocks.com>, Jui-Wen Chen <jui-wen.chen@useblocks.com>
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "sphinx-codelinks"
3
- version = "0.1.1"
3
+ version = "0.1.2"
4
4
  description = "Fast Source Code Traceability for Sphinx-Needs"
5
5
  authors = [{ name = "team useblocks", email = "info@useblocks.com" }]
6
6
  maintainers = [
@@ -26,7 +26,7 @@ app = typer.Typer(
26
26
 
27
27
  @app.command(no_args_is_help=True)
28
28
  def discover(
29
- root_dir: Annotated[
29
+ src_dir: Annotated[
30
30
  Path,
31
31
  typer.Argument(
32
32
  ...,
@@ -68,7 +68,7 @@ def discover(
68
68
  from sphinx_codelinks.source_discovery.source_discover import SourceDiscover
69
69
 
70
70
  source_discover = SourceDiscover(
71
- root_dir=root_dir,
71
+ src_dir=src_dir,
72
72
  exclude=exclude,
73
73
  include=include,
74
74
  file_types=file_types,
@@ -134,8 +134,8 @@ def vdoc(
134
134
  if oneline_errors:
135
135
  errors.appendleft("Invalid oneline comment style configuration:")
136
136
  errors.extend(oneline_errors)
137
-
138
- src_discovery_dict, src_discovery_errors = build_src_discovery_dict(project_config)
137
+ src_discovery_errors = []
138
+ src_discovery_dict = build_src_discovery_dict(project_config)
139
139
  if src_discovery_dict:
140
140
  src_discovery_config = SourceDiscoveryConfig(**src_discovery_dict)
141
141
  else:
@@ -151,9 +151,9 @@ def vdoc(
151
151
 
152
152
  from sphinx_codelinks.source_discovery.source_discover import SourceDiscover
153
153
 
154
- src_root_dir = (config.parent / src_discovery_config.root_dir).resolve()
154
+ src_dir = (config.parent / src_discovery_config.src_dir).resolve()
155
155
  source_discover = SourceDiscover(
156
- root_dir=src_root_dir,
156
+ src_dir=src_dir,
157
157
  exclude=src_discovery_config.exclude,
158
158
  include=src_discovery_config.include,
159
159
  file_types=src_discovery_config.file_types,
@@ -164,7 +164,7 @@ def vdoc(
164
164
 
165
165
  virtual_docs = VirtualDocs(
166
166
  src_files=source_discover.source_paths,
167
- src_dir=str(src_root_dir),
167
+ src_dir=str(src_dir),
168
168
  output_dir=str(output_dir),
169
169
  oneline_comment_style=oneline_comment_style,
170
170
  comment_type=src_discovery_config.file_types[0]
@@ -178,7 +178,7 @@ def vdoc(
178
178
  typer.echo("The virtual documents are generated:")
179
179
  for v_doc in virtual_docs.virtual_docs:
180
180
  json_path = output_dir / v_doc.filepath.with_suffix(".json").relative_to(
181
- src_root_dir
181
+ src_dir
182
182
  )
183
183
  typer.echo(json_path)
184
184
  else:
@@ -6,7 +6,7 @@ from jsonschema import ValidationError, validate
6
6
 
7
7
 
8
8
  class SourceDiscoveryConfigType(TypedDict, total=False):
9
- root_dir: Path
9
+ src_dir: Path
10
10
  exclude: list[str]
11
11
  include: list[str]
12
12
  gitignore: bool
@@ -19,8 +19,8 @@ class SourceDiscoveryConfig:
19
19
  def field_names(cls) -> set[str]:
20
20
  return {item.name for item in fields(cls)}
21
21
 
22
- root_dir: Path = field(
23
- default_factory=lambda: Path.cwd(), metadata={"schema": {"type": "string"}}
22
+ src_dir: Path = field(
23
+ default_factory=lambda: Path("./"), metadata={"schema": {"type": "string"}}
24
24
  )
25
25
  """The root of the source directory."""
26
26
 
@@ -41,7 +41,9 @@ class SourceDiscoveryConfig:
41
41
 
42
42
  file_types: list[str] = field(
43
43
  default_factory=lambda: ["c", "h", "cpp", "hpp"],
44
- metadata={"schema": {"type": "array", "items": {"type": "string"}}},
44
+ metadata={
45
+ "schema": {"type": "array", "items": {"enum": ["c", "h", "cpp", "hpp"]}}
46
+ },
45
47
  )
46
48
  """The file types to discover."""
47
49
 
@@ -9,13 +9,13 @@ from gitignore_parser import parse_gitignore # type: ignore[import-untyped]
9
9
  class SourceDiscover:
10
10
  def __init__(
11
11
  self,
12
- root_dir: Path,
12
+ src_dir: Path,
13
13
  exclude: list[str] | None = None,
14
14
  include: list[str] | None = None,
15
15
  gitignore: bool = True,
16
16
  file_types: list[str] | None = None,
17
17
  ):
18
- self.root_path = root_dir
18
+ self.root_path = src_dir
19
19
  self.exclude = exclude
20
20
  self.include = include
21
21
  # Only gitignore at source root is considered.
@@ -43,7 +43,7 @@ class SrcTraceProjectConfigFileType(TypedDict):
43
43
  class SrcTraceProjectConfigType(TypedDict):
44
44
  # only support C/C++ for now
45
45
  comment_type: Literal["cpp", "hpp", "c", "h"]
46
- src_dir: str
46
+ src_dir: Path
47
47
  remote_url_pattern: str
48
48
  exclude: list[str]
49
49
  include: list[str]
@@ -241,9 +241,8 @@ def check_project_configuration(config: SrcTraceSphinxConfig) -> list[str]:
241
241
  for project_name, project_config in config.projects.items():
242
242
  project_errors: list[str] = []
243
243
  oneline_errors = validate_oneline_comment_style(project_config)
244
- src_discovery_dict, src_discovery_errors = build_src_discovery_dict(
245
- project_config
246
- )
244
+ src_discovery_dict = build_src_discovery_dict(project_config)
245
+ src_discovery_errors = []
247
246
  if src_discovery_dict is not None:
248
247
  src_discovery_config = SourceDiscoveryConfig(**src_discovery_dict)
249
248
  src_discovery_errors.extend(src_discovery_config.check_schema())
@@ -286,22 +285,40 @@ def validate_oneline_comment_style(
286
285
 
287
286
  def build_src_discovery_dict(
288
287
  project_config: SrcTraceProjectConfigType,
289
- ) -> tuple[SourceDiscoveryConfigType | None, list[str]]:
288
+ ) -> SourceDiscoveryConfigType | None:
290
289
  src_discovery_dict = cast(SourceDiscoveryConfigType, {})
291
- src_discovery_errors = []
292
- if "src_dir" in project_config:
293
- if isinstance(project_config["src_dir"], str):
294
- src_discovery_dict["root_dir"] = Path(project_config["src_dir"])
295
- else:
296
- src_discovery_errors.append("src_dir must be a string")
297
- for key in ("exclude", "include", "gitignore"):
290
+ # adapt the configs between source tracing and source discovery
291
+ if "comment_type" in project_config:
292
+ # comment type error will be taken care by SourceDiscovery class later
293
+ src_discovery_dict["file_types"] = (
294
+ list(SUPPORTED_COMMENT_TYPES)
295
+ if project_config["comment_type"] in SUPPORTED_COMMENT_TYPES
296
+ else [project_config["comment_type"]]
297
+ )
298
+ for key in ("exclude", "include", "gitignore", "src_dir"):
298
299
  if key in project_config:
299
300
  src_discovery_dict[key] = project_config[key]
300
- if "comment_type" in project_config:
301
- if project_config["comment_type"] not in SUPPORTED_COMMENT_TYPES:
302
- src_discovery_errors.append(
303
- f"comment_type must be one of {sorted(SUPPORTED_COMMENT_TYPES)}"
304
- )
305
- else:
306
- src_discovery_dict["file_types"] = list(SUPPORTED_COMMENT_TYPES)
307
- return src_discovery_dict, src_discovery_errors
301
+
302
+ return src_discovery_dict
303
+
304
+
305
+ def adpat_src_discovery_config(project_config: SrcTraceProjectConfigType) -> None:
306
+ src_discovery_dict = build_src_discovery_dict(project_config)
307
+ if src_discovery_dict:
308
+ src_discovery_config = SourceDiscoveryConfig(**src_discovery_dict)
309
+ else:
310
+ src_discovery_config = SourceDiscoveryConfig()
311
+
312
+ for _field in fields(src_discovery_config):
313
+ key = "comment_type" if _field.name == "file_types" else _field.name
314
+
315
+ if key == "comment_type":
316
+ file_types = getattr(src_discovery_config, _field.name)
317
+ if set(file_types) == SUPPORTED_COMMENT_TYPES:
318
+ comment_type = "cpp"
319
+ else:
320
+ comment_type = file_types[0]
321
+ project_config[key] = comment_type # type: ignore[literal-required] # dynamically assign
322
+ continue
323
+
324
+ project_config[key] = getattr(src_discovery_config, _field.name) # type: ignore[literal-required] # dynamically assign
@@ -1,4 +1,5 @@
1
1
  from collections.abc import Callable
2
+ import os
2
3
  from pathlib import Path
3
4
  import subprocess
4
5
  from typing import Any, ClassVar, cast
@@ -35,13 +36,23 @@ logger = logging.getLogger(__name__)
35
36
 
36
37
 
37
38
  def generate_str_link_name(
38
- comment: UBTComment, target_filepath: Path, target_dir: str
39
+ comment: UBTComment,
40
+ target_filepath: Path,
41
+ dirs: dict[str, Path],
42
+ local: bool = False,
39
43
  ) -> str:
40
44
  if comment.start_line == comment.end_line:
41
45
  lineno = f"L{comment.start_line}"
42
46
  else:
43
47
  lineno = f"L{comment.start_line}-L{comment.end_line}"
44
- url = str(target_filepath.relative_to(target_dir)) + f"#{lineno}"
48
+ # url = str(target_filepath.relative_to(target_dir)) + f"#{lineno}"
49
+ if local:
50
+ url = str(target_filepath) + f"#{lineno}"
51
+ else:
52
+ remote_path = dirs["remote_src_dir"] / target_filepath.relative_to(
53
+ dirs["target_dir"]
54
+ )
55
+ url = f"{remote_path!s}#{lineno}"
45
56
 
46
57
  return url
47
58
 
@@ -151,6 +162,12 @@ class SourceTracingDirective(SphinxDirective):
151
162
  )
152
163
  needs.extend(src_trace_need)
153
164
 
165
+ dirs = {
166
+ "src_dir": src_dir,
167
+ "out_dir": out_dir,
168
+ "target_dir": target_dir,
169
+ }
170
+
154
171
  # inject needs_string_links config before add_need()
155
172
  # https://sphinx-needs.readthedocs.io/en/latest/configuration.html#needs-string-links
156
173
  # local URL
@@ -158,12 +175,13 @@ class SourceTracingDirective(SphinxDirective):
158
175
  remote_url_field = None
159
176
  if src_trace_sphinx_config.set_local_url:
160
177
  local_url_field = src_trace_sphinx_config.local_url_field
178
+ to_remove_str = f"{out_dir!s}{os.sep}"
179
+ if os.name == "nt":
180
+ to_remove_str = to_remove_str.replace("\\", "\\\\")
161
181
  self.env.config.needs_string_links[local_url_field] = {
162
182
  "regex": r"^(?P<value>.+?)\.[^\.]+#L(?P<lineno>\d+)",
163
- "link_url": (
164
- f"file://{target_dir!s}/{{{{value}}}}.html#L-{{{{lineno}}}}"
165
- ),
166
- "link_name": "{{value}}#L{{lineno}}",
183
+ "link_url": ("file://{{value}}.html#L-{{lineno}}"),
184
+ "link_name": f"{{{{value | replace('{to_remove_str}', '')}}}}#L{{{{lineno}}}}",
167
185
  "options": [local_url_field],
168
186
  }
169
187
  if (
@@ -178,9 +196,11 @@ class SourceTracingDirective(SphinxDirective):
178
196
  remote_src_dir = src_dir
179
197
  else:
180
198
  remote_src_dir = src_dir.relative_to(git_root_path)
199
+ dirs["remote_src_dir"] = remote_src_dir
181
200
  remote_url_pattern = src_trace_conf["remote_url_pattern"].format(
182
201
  commit=commit_id,
183
- path=f"{remote_src_dir}/" + "{{value}}",
202
+ # path=f"{remote_src_dir}/" + "{{value}}",
203
+ path="{{value}}",
184
204
  line="{{lineno}}",
185
205
  )
186
206
  self.env.config.needs_string_links[remote_url_field] = {
@@ -189,11 +209,7 @@ class SourceTracingDirective(SphinxDirective):
189
209
  "link_name": "{{value}}#L{{lineno}}",
190
210
  "options": [remote_url_field],
191
211
  }
192
- dirs = {
193
- "src_dir": src_dir,
194
- "out_dir": out_dir,
195
- "target_dir": target_dir,
196
- }
212
+
197
213
  # render needs from the source files
198
214
  rendered_needs = self.render_needs(
199
215
  virtual_docs,
@@ -287,13 +303,18 @@ class SourceTracingDirective(SphinxDirective):
287
303
  target_filepath.parent.mkdir(parents=True, exist_ok=True)
288
304
  target_filepath.write_text(filepath.read_text())
289
305
  for comment in virtual_doc.comments:
290
- # Always generate link_name to avoid unbound errors
291
- link_name = None
292
- if local_url_field or remote_url_field:
306
+ local_link_name = None
307
+ remote_link_name = None
308
+ if local_url_field:
293
309
  # generate link name
294
- link_name = generate_str_link_name(
295
- comment, target_filepath, str(dirs["target_dir"])
310
+ local_link_name = generate_str_link_name(
311
+ comment, target_filepath, dirs, local=True
296
312
  )
313
+ if remote_url_field:
314
+ remote_link_name = generate_str_link_name(
315
+ comment, target_filepath, dirs, local=False
316
+ )
317
+
297
318
  if comment.resolved_marker:
298
319
  # render needs from one-line marker
299
320
  kwargs: dict[str, str | list[str]] = {
@@ -306,12 +327,12 @@ class SourceTracingDirective(SphinxDirective):
306
327
  ] # title and type are mandatory for add_need()
307
328
  }
308
329
 
309
- if local_url_field and link_name is not None:
310
- kwargs[local_url_field] = link_name
311
- if remote_url_field and link_name is not None:
312
- kwargs[remote_url_field] = link_name
330
+ if local_url_field and local_link_name is not None:
331
+ kwargs[local_url_field] = local_link_name
332
+ if remote_url_field and remote_link_name is not None:
333
+ kwargs[remote_url_field] = remote_link_name
313
334
 
314
- marker_needs: list[nodes.Node] = add_need(
335
+ oneline_needs: list[nodes.Node] = add_need(
315
336
  app=self.env.app, # The Sphinx application object
316
337
  state=self.state, # The docutils state object
317
338
  docname=self.env.docname, # The current document name
@@ -324,7 +345,7 @@ class SourceTracingDirective(SphinxDirective):
324
345
  ), # The title of the need
325
346
  **cast(dict[str, Any], kwargs), # type: ignore[explicit-any]
326
347
  )
327
- rendered_needs.extend(marker_needs)
348
+ rendered_needs.extend(oneline_needs)
328
349
  if local_url_field:
329
350
  # save the mapping of need links and line numbers of source codes
330
351
  # for the later use in `html-collect-pages`
@@ -21,6 +21,7 @@ from sphinx_codelinks.sphinx_extension.config import (
21
21
  SrcTraceConfigType,
22
22
  SrcTraceProjectConfigType,
23
23
  SrcTraceSphinxConfig,
24
+ adpat_src_discovery_config,
24
25
  check_configuration,
25
26
  file_lineno_href,
26
27
  )
@@ -155,6 +156,10 @@ def set_config_to_sphinx(
155
156
  for project_config in cast(
156
157
  dict[str, SrcTraceProjectConfigType], value
157
158
  ).values():
159
+ # address SourceDiscovery related config
160
+ adpat_src_discovery_config(project_config)
161
+
162
+ # address OneLoneCommenyStyle config and its default
158
163
  oneline_comment_style: OneLineCommentStyleType | None = cast(
159
164
  OneLineCommentStyleType, project_config.get("oneline_comment_style")
160
165
  )
@@ -165,6 +170,8 @@ def set_config_to_sphinx(
165
170
  project_config["oneline_comment_style"],
166
171
  )
167
172
  )
173
+ else:
174
+ project_config["oneline_comment_style"] = OneLineCommentStyle()
168
175
 
169
176
  config[f"src_trace_{key}"] = value
170
177