Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 18 additions & 11 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ on:
push:
branches: [master]
pull_request:
jobs:

jobs:
lint:
name: Lint
runs-on: ubuntu-latest
Expand All @@ -23,18 +23,25 @@ jobs:
fail-fast: false # Set on "false" to get the results of ALL builds
matrix:
os: ["ubuntu-latest"]
python-version: ["3.10", "3.12"]
sphinx-version: ["7.0", "8.0"]
python-version: ["3.9", "3.12", "3.13"]
sphinx-version: ["7.4", "8.2"]
include:
- os: "ubuntu-latest"
python-version: "3.9"
sphinx-version: "7.0"
# corner cases for Windows
- os: "windows-latest"
python-version: "3.9"
sphinx-version: "7.0"
sphinx-version: "7.4"
- os: "windows-latest"
python-version: "3.12"
sphinx-version: "8.0"
sphinx-version: "8.2"
- os: "windows-latest"
python-version: "3.13"
sphinx-version: "8.2"
exclude:
# Sphinx 8.2 only supports py3.11+
- os: "ubuntu-latest"
python-version: "3.9"
sphinx-version: "8.2"

steps:
- uses: actions/checkout@v4
- name: Install graphviz (linux)
Expand Down Expand Up @@ -75,10 +82,10 @@ jobs:
include:
- os: "ubuntu-latest"
python-version: "3.9"
sphinx-version: "7.0"
sphinx-version: "7.4"
- os: "ubuntu-latest"
python-version: "3.12"
sphinx-version: "8.0"
python-version: "3.13"
sphinx-version: "8.2"
steps:
- uses: actions/checkout@v4
- name: Use Node.js
Expand Down
20 changes: 14 additions & 6 deletions docs/directives/needimport.rst
Original file line number Diff line number Diff line change
Expand Up @@ -26,26 +26,34 @@ The directive argument can be one of the following formats:
- A remote URL from which to download the ``needs.json``:

.. code-block:: rst

.. needimport:: https://my_company.com/docs/remote-needs.json

- A local path relative to the containing document:

.. code-block:: rst

.. needimport:: needs.json

- A local path starting with ``/`` is relative to the Sphinx source directory:

.. code-block:: rst

.. needimport:: /path/to/needs.json

- For an absolute path, make sure to start with two ``//`` (on Linux/OSX):
- For an absolute path on Linux/OSX, make sure to start with two ``//``:

.. code-block:: rst

.. needimport:: //absoulte/path/to/needs.json

.. needimport:: //absolute/path/to/needs.json

- For an absolute path on Windows, just use the normal drive letters with either forward or backward slashes:

.. code-block:: rst

.. needimport:: c:/absolute/path/to/needs.json

.. needimport:: c:\absolute\path\to\needs.json

Options
-------
Expand Down
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,14 @@ classifiers = [
'Programming Language :: Python :: 3.10',
'Programming Language :: Python :: 3.11',
'Programming Language :: Python :: 3.12',
'Programming Language :: Python :: 3.13',
'Topic :: Documentation',
'Topic :: Utilities',
'Framework :: Sphinx :: Extension',
]
requires-python = ">=3.9,<4"
dependencies = [
"sphinx>=7.0,<9",
"sphinx>=7.4,<9",
"requests-file~=2.1", # external links
"requests~=2.32", # external links
"jsonschema>=3.2.0", # needsimport schema validation
Expand Down
31 changes: 3 additions & 28 deletions sphinx_needs/directives/needimport.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,34 +81,9 @@ def run(self) -> Sequence[nodes.Node]:
else:
logger.info(f"Importing needs from {need_import_path}")

if not os.path.isabs(need_import_path):
# Relative path should start from current rst file directory
curr_dir = os.path.dirname(self.docname)
new_need_import_path = os.path.join(
self.env.app.srcdir, curr_dir, need_import_path
)

correct_need_import_path = new_need_import_path
if not os.path.exists(new_need_import_path):
# Check the old way that calculates relative path starting from conf.py directory
old_need_import_path = os.path.join(
self.env.app.srcdir, need_import_path
)
if os.path.exists(old_need_import_path):
correct_need_import_path = old_need_import_path
log_warning(
logger,
"Deprecation warning: Relative path must be relative to the current document in future, "
"not to the conf.py location. Use a starting '/', like '/needs.json', to make the path "
"relative to conf.py.",
"deprecated",
location=(self.env.docname, self.lineno),
)
else:
# Absolute path starts with /, based on the source directory. The / need to be striped
correct_need_import_path = os.path.join(
self.env.app.srcdir, need_import_path[1:]
)
correct_need_import_path = self.env.relfn2path(
need_import_path, self.env.docname
)[1]

if not os.path.exists(correct_need_import_path):
raise ReferenceError(
Expand Down
5 changes: 3 additions & 2 deletions sphinx_needs/directives/needuml.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import os
import time
from collections.abc import Sequence
from pathlib import PurePosixPath
from typing import TYPE_CHECKING, Any, TypedDict

from docutils import nodes
Expand Down Expand Up @@ -107,9 +108,9 @@ def run(self) -> Sequence[nodes.Node]:
save_path = self.options.get("save")
plantuml_code_out_path = None
if save_path:
if os.path.isabs(save_path):
if PurePosixPath(save_path).is_absolute():
raise NeedumlException(
f"Given save path: {save_path}, is not a relative path."
f"Given save path: {save_path}, is not a relative posix path."
)
else:
plantuml_code_out_path = save_path
Expand Down
13 changes: 13 additions & 0 deletions sphinx_needs/needs.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@
from docutils import nodes
from docutils.parsers.rst import directives
from sphinx.application import Sphinx
from sphinx.builders import Builder
from sphinx.config import Config
from sphinx.environment import BuildEnvironment
from sphinx.errors import SphinxError

import sphinx_needs.debug as debug # Need to set global var in it for timeing measurements
from sphinx_needs import __version__
from sphinx_needs.api import get_needs_view
from sphinx_needs.api.need import _split_list_with_dyn_funcs
from sphinx_needs.builder import (
NeedsBuilder,
Expand Down Expand Up @@ -305,6 +307,8 @@ def setup(app: Sphinx) -> dict[str, Any]:
app.connect("doctree-resolved", process_need_nodes)
app.connect("doctree-resolved", process_creator(NODE_TYPES))

app.connect("write-started", ensure_post_process_needs_data)

app.connect("build-finished", process_warnings)
app.connect("build-finished", build_needs_json)
app.connect("build-finished", build_needs_id_json)
Expand All @@ -324,6 +328,15 @@ def setup(app: Sphinx) -> dict[str, Any]:
}


def ensure_post_process_needs_data(app: Sphinx, builder: Builder) -> None:
"""
Make sure post_process_needs_data is called at least once.

Warnings are emitted in that step, even when no docs are updated.
"""
get_needs_view(app)


def process_creator(
node_list: _NODE_TYPES_T, doc_category: str = "all"
) -> Callable[[Sphinx, nodes.document, str], None]:
Expand Down
21 changes: 3 additions & 18 deletions tests/__snapshots__/test_needimport.ambr
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@
'external_css': 'external_link',
'id': 'REQ_1',
'layout': '',
'lineno': 48,
'lineno': 47,
'section_name': 'FILTERED',
'sections': list([
'FILTERED',
Expand Down Expand Up @@ -193,7 +193,7 @@
'external_css': 'external_link',
'id': 'SPEC_1',
'layout': '',
'lineno': 51,
'lineno': 50,
'section_name': 'FILTERED',
'sections': list([
'FILTERED',
Expand Down Expand Up @@ -1250,21 +1250,6 @@
'type': 'req',
'type_name': 'Requirement',
}),
'small_depr_rel_path_TEST_01': dict({
'content': 'small_depr_rel_path_TEST_01',
'docname': 'subdoc/deprecated_rel_path_import',
'external_css': 'external_link',
'id': 'small_depr_rel_path_TEST_01',
'is_import': True,
'lineno': 6,
'section_name': 'Deprecated Relative path import test',
'sections': list([
'Deprecated Relative path import test',
]),
'title': 'TEST_01 DESCRIPTION',
'type': 'impl',
'type_name': 'Implementation',
}),
'small_rel_path_TEST_01': dict({
'content': 'small_rel_path_TEST_01',
'docname': 'subdoc/rel_path_import',
Expand Down Expand Up @@ -1635,7 +1620,7 @@
'type_name': 'Test Case',
}),
}),
'needs_amount': 66,
'needs_amount': 65,
'needs_defaults_removed': True,
'needs_schema': dict({
'$schema': 'http://json-schema.org/draft-07/schema#',
Expand Down
26 changes: 24 additions & 2 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,19 @@ def copy_srcdir_to_tmpdir(srcdir: path, tmp: path) -> path:
return tmproot


def create_src_files_in_tmpdir(files: list[tuple[Path, str]], tmp: path) -> path:
"""Create source files in a temporary directory under the subdir src."""
subdir = path("src")
tmproot = tmp.joinpath(generate_random_string()) / subdir
tmproot.makedirs(exist_ok=True)
for file in files:
file_path, content = file
file_abs = tmproot.joinpath(str(file_path))
file_abs.parent.makedirs(exist_ok=True)
file_abs.write_text(content)
return tmproot


def get_abspath(relpath: str) -> str:
"""
Get the absolute path from a relative path.
Expand Down Expand Up @@ -264,9 +277,18 @@ def test_app(make_app, sphinx_test_tempdir, request):
)
sphinx_conf_overrides.update(plantuml=plantuml)

# copy test srcdir to test temporary directory sphinx_test_tempdir
srcdir = builder_params.get("srcdir")
src_dir = copy_srcdir_to_tmpdir(srcdir, sphinx_test_tempdir)
files = builder_params.get("files")
if (srcdir is None) == (files is None):
raise ValueError("Exactly one of srcdir, files must not be None")

if srcdir is not None:
# copy test srcdir to test temporary directory sphinx_test_tempdir
src_dir = copy_srcdir_to_tmpdir(srcdir, sphinx_test_tempdir)
else:
# create given files in tmpdir
src_dir = create_src_files_in_tmpdir(files, sphinx_test_tempdir)

parent_path = Path(str(src_dir.parent.abspath()))

if version_info >= (7, 2):
Expand Down
1 change: 0 additions & 1 deletion tests/doc_test/import_doc/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ FILTERED
subdoc/filter
subdoc/abs_path_import
subdoc/rel_path_import
subdoc/deprecated_rel_path_import

.. req:: Test requirement 1
:id: REQ_1
Expand Down

This file was deleted.

Loading