diff --git a/.gitignore b/.gitignore index 31a9ad20..8d078321 100644 --- a/.gitignore +++ b/.gitignore @@ -118,6 +118,7 @@ dmypy.json # Documentation /site index.md +/api/ docs/_build/ docs/api docs/html diff --git a/docs/src/api.md b/docs/src/api.md index 8a352901..f21f8337 100644 --- a/docs/src/api.md +++ b/docs/src/api.md @@ -61,46 +61,18 @@ Or document specific classes with `doxygenclass`: ## Rust Example -This is an example of documenting Rust code using sphinx-rust integration. +This is an example of documenting Rust code using sphinxcontrib-rust integration. -### Document a Crate - -Use `rust:crate` to document an entire Rust crate: - -```{eval-rst} -.. rust:crate:: calculator - -``` +Unlike the C++ and JavaScript examples below, the Rust pages are not written by +hand. `sphinx-rustdocgen` reads each crate listed under +`[tool.yardang.sphinx-rust]` and writes a page per module into the configured +`doc-dir`, so the only thing to author is a toctree pointing at the result: ```{toctree} :hidden: :maxdepth: 2 -/api/crates/calculator/index -``` - -### Document Individual Items - -Or document specific structs, enums, and functions: - -```{eval-rst} -.. rust:struct:: calculator::Calculator - -``` - -```{eval-rst} -.. rust:struct:: calculator::ScientificCalculator - -``` - -```{eval-rst} -.. rust:enum:: calculator::Operation - -``` - -```{eval-rst} -.. rust:enum:: calculator::CalculatorError - +/api/calculator/lib ``` ## JavaScript Example diff --git a/docs/src/configuration.md b/docs/src/configuration.md index 351a5784..9842747a 100644 --- a/docs/src/configuration.md +++ b/docs/src/configuration.md @@ -705,44 +705,68 @@ Then in your documentation files, you can use breathe directives: ## Sphinx-Rust Integration -Yardang provides integration with [sphinx-rust](https://sphinx-rust.readthedocs.io/) for documenting Rust code. To use this feature, install yardang with the sphinx-rust extra: +Yardang provides integration with [sphinxcontrib-rust](https://gitlab.com/munir0b0t/sphinxcontrib-rust) for documenting Rust code. To use this feature, install yardang with the rust extra: ```bash -pip install yardang[sphinx-rust] +pip install yardang[rust] ``` -All sphinx-rust configuration is under `[tool.yardang.sphinx-rust]`. +This extra is GPL-3.0 licensed, unlike yardang itself, which is Apache-2.0. It also builds a `sphinx-rustdocgen` helper binary on install, so a Rust toolchain must be available. + +All configuration is under `[tool.yardang.sphinx-rust]`. ### `crates` -A list of paths to Rust crates to document. +A mapping of crate name to crate directory. Every crate listed here is scanned and documented. ```toml [tool.yardang.sphinx-rust] -crates = [ - "path/to/crate1", - "path/to/crate2", -] +crates = { crate1 = "path/to/crate1", crate2 = "path/to/crate2" } +``` + +### `doc-dir` + +Where the generated pages are written, relative to the documentation root. Defaults to `"api"`, producing `api//lib.rst` for each crate. + +```toml +[tool.yardang.sphinx-rust] +doc-dir = "api" ``` -### `doc-formats` +### `rustdoc-fmt` -A dictionary mapping crate names to their docstring format. Valid values are `"restructuredtext"` (default) or `"myst-nb"` (for markdown docstrings). +The markup used inside Rust doc comments, either `"rst"` (default) or `"md"`. Accepts a single value for all crates, or a per-crate mapping. ```toml [tool.yardang.sphinx-rust] -doc-formats = { mycrate = "myst-nb" } +rustdoc-fmt = { mycrate = "md" } ``` -**Note:** When using `myst_nb` as your Sphinx parser (which yardang uses by default), use `"myst-nb"` instead of `"markdown"` for markdown docstrings. +### `visibility` -### `viewcode` +The minimum item visibility to document. Defaults to `"pub"`; use `"crate"` or `"pvt"` to include less visible items. -Enable links to the source code for documented items. Defaults to `true`. +```toml +[tool.yardang.sphinx-rust] +visibility = "pub" +``` + +### `strip-src` + +Strip the `src` directory from generated paths. Defaults to `true`. ```toml [tool.yardang.sphinx-rust] -viewcode = true +strip-src = true +``` + +### `generate-mode` + +When to regenerate pages: `"changed"` (default), `"always"`, or `"skip"`. + +```toml +[tool.yardang.sphinx-rust] +generate-mode = "changed" ``` ### Complete Example @@ -757,41 +781,21 @@ pages = ["docs/api.md", "docs/examples.md"] use-autoapi = false [tool.yardang.sphinx-rust] -crates = [ - "crates/mylib", - "crates/mylib-utils", -] -doc-formats = { mylib = "myst-nb", "mylib-utils" = "restructuredtext" } -viewcode = true +crates = { mylib = "crates/mylib", mylib-utils = "crates/mylib-utils" } +doc-dir = "api" +rustdoc-fmt = { mylib = "md" } ``` -Then in your documentation files, you can use sphinx-rust directives: +The pages are generated for you, so your documentation files only need a toctree entry pointing at them: ````markdown # API Reference -## Document a Crate - -\`\`\`{eval-rst} -.. rust:crate:: mylib - -\`\`\` - -## Document Individual Items - -\`\`\`{eval-rst} -.. rust:struct:: mylib::MyStruct - -\`\`\` - -\`\`\`{eval-rst} -.. rust:enum:: mylib::MyEnum - -\`\`\` - -\`\`\`{eval-rst} -.. rust:function:: mylib::my_function +\`\`\`{toctree} +:maxdepth: 2 +/api/mylib/lib +/api/mylib-utils/lib \`\`\` ```` diff --git a/pyproject.toml b/pyproject.toml index 68c593d6..56ee5a7d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -52,7 +52,7 @@ breathe = [ "breathe>=4.35.0", ] rust = [ - "sphinx-rust", + "sphinxcontrib-rust", ] js = [ "sphinx-js>=5.0.0", @@ -82,7 +82,7 @@ develop = [ "wheel", # Extensions "breathe>=4.35.0", - "sphinx-rust", + "sphinxcontrib-rust", "sphinx-js>=5.0.0", # Themes "shibuya", @@ -206,10 +206,8 @@ projects = { calculator = "examples/cpp/xml" } default-project = "calculator" [tool.yardang.sphinx-rust] -crates = [ - "examples/rust", -] -doc-formats = { calculator = "myst-nb" } +crates = { calculator = "examples/rust" } +doc-dir = "api" [tool.yardang.sphinx-js] js-language = "javascript" diff --git a/yardang/build.py b/yardang/build.py index 353fc8c8..5fd7e153 100644 --- a/yardang/build.py +++ b/yardang/build.py @@ -345,18 +345,18 @@ def customize(args): "doxygendefine", "doxygenunion", "doxygenvariable", - # sphinx-rust directives + # sphinxcontrib-rust directives "rust:crate", "rust:module", "rust:struct", "rust:enum", "rust:function", - "rust:method", + "rust:executable", "rust:trait", "rust:impl", "rust:type", - "rust:const", - "rust:static", + "rust:use", + "rust:variable", "rust:macro", # sphinx-js directives "js:autofunction", @@ -440,14 +440,17 @@ def customize(args): name: str(Path(path).resolve()) if not Path(path).is_absolute() else path for name, path in breathe_args["breathe_projects"].items() } - # Load sphinx-rust configuration from tool.yardang.sphinx-rust + # Load sphinxcontrib-rust configuration from tool.yardang.sphinx-rust rust_config_base = f"{config_base}.sphinx-rust" rust_args = {} for config_option, default in { - # sphinx-rust - "rust_crates": [], - "rust_doc_formats": {}, - "rust_viewcode": True, + # sphinxcontrib-rust + "rust_crates": {}, + "rust_doc_dir": "api", + "rust_rustdoc_fmt": "rst", + "rust_visibility": "pub", + "rust_strip_src": True, + "rust_generate_mode": "changed", }.items(): # config keys in toml use hyphens, not underscores, and no rust_ prefix toml_key = config_option.replace("rust_", "").replace("_", "-") @@ -455,12 +458,14 @@ def customize(args): if rust_args[config_option] is None: rust_args[config_option] = default - # Determine if sphinx-rust should be used + # Determine if sphinxcontrib-rust should be used use_sphinx_rust = bool(rust_args["rust_crates"]) - # Convert relative paths in rust_crates to absolute paths + # Crates are a name -> directory mapping; the generator needs absolute paths if rust_args["rust_crates"]: - rust_args["rust_crates"] = [str(Path(path).resolve()) if not Path(path).is_absolute() else path for path in rust_args["rust_crates"]] + rust_args["rust_crates"] = { + name: str(Path(path).resolve()) if not Path(path).is_absolute() else path for name, path in rust_args["rust_crates"].items() + } # Load sphinx-js configuration from tool.yardang.sphinx-js js_config_base = f"{config_base}.sphinx-js" diff --git a/yardang/conf.py.j2 b/yardang/conf.py.j2 index a92a879b..b7b3239d 100644 --- a/yardang/conf.py.j2 +++ b/yardang/conf.py.j2 @@ -92,9 +92,9 @@ if use_search: if use_breathe: extensions.append("breathe") -# Add sphinx-rust extension if configured +# Add sphinxcontrib-rust extension if configured if use_sphinx_rust: - extensions.append("sphinx_rust") + extensions.append("sphinxcontrib_rust") # Add sphinx-js extension if configured if use_sphinx_js: @@ -223,11 +223,22 @@ if use_breathe: breathe_order_parameters_first = {{breathe_order_parameters_first}} breathe_separate_member_pages = {{breathe_separate_member_pages}} -# sphinx-rust configuration +# sphinxcontrib-rust configuration if use_sphinx_rust: rust_crates = {{rust_crates}} - rust_doc_formats = {{rust_doc_formats}} - rust_viewcode = {{rust_viewcode}} + rust_doc_dir = "{{rust_doc_dir}}" + {% if rust_rustdoc_fmt is string %} + rust_rustdoc_fmt = "{{rust_rustdoc_fmt}}" + {% else %} + rust_rustdoc_fmt = {{rust_rustdoc_fmt}} + {% endif %} + {% if rust_visibility is string %} + rust_visibility = "{{rust_visibility}}" + {% else %} + rust_visibility = {{rust_visibility}} + {% endif %} + rust_strip_src = {{rust_strip_src}} + rust_generate_mode = "{{rust_generate_mode}}" # sphinx-js configuration if use_sphinx_js: diff --git a/yardang/tests/test_breathe.py b/yardang/tests/test_breathe.py index afaaaa6b..ae7d2676 100644 --- a/yardang/tests/test_breathe.py +++ b/yardang/tests/test_breathe.py @@ -472,10 +472,10 @@ def test_auto_run_doxygen_config_option(self, tmp_path): class TestSphinxRustConfiguration: - """Tests for sphinx-rust configuration loading and generation.""" + """Tests for sphinxcontrib-rust configuration loading and generation.""" def test_sphinx_rust_config_loading_from_pyproject(self, tmp_path): - """Test that sphinx-rust configuration is loaded from pyproject.toml.""" + """Test that sphinxcontrib-rust configuration is loaded from pyproject.toml.""" pyproject_content = """ [project] name = "test-project" @@ -487,9 +487,10 @@ def test_sphinx_rust_config_loading_from_pyproject(self, tmp_path): use-autoapi = false [tool.yardang.sphinx-rust] -crates = ["crates/mylib", "crates/otherlib"] -doc-formats = { "mylib" = "markdown", "otherlib" = "restructuredtext" } -viewcode = true +crates = { mylib = "crates/mylib", otherlib = "crates/otherlib" } +doc-dir = "api" +rustdoc-fmt = { "mylib" = "md", "otherlib" = "rst" } +visibility = "pub" """ pyproject_path = tmp_path / "pyproject.toml" pyproject_path.write_text(pyproject_content) @@ -504,18 +505,21 @@ def test_sphinx_rust_config_loading_from_pyproject(self, tmp_path): from yardang.utils import get_config rust_crates = get_config(section="crates", base="tool.yardang.sphinx-rust") - assert rust_crates == ["crates/mylib", "crates/otherlib"] + assert rust_crates == {"mylib": "crates/mylib", "otherlib": "crates/otherlib"} - doc_formats = get_config(section="doc-formats", base="tool.yardang.sphinx-rust") - assert doc_formats == {"mylib": "markdown", "otherlib": "restructuredtext"} + doc_dir = get_config(section="doc-dir", base="tool.yardang.sphinx-rust") + assert doc_dir == "api" - viewcode = get_config(section="viewcode", base="tool.yardang.sphinx-rust") - assert viewcode is True + rustdoc_fmt = get_config(section="rustdoc-fmt", base="tool.yardang.sphinx-rust") + assert rustdoc_fmt == {"mylib": "md", "otherlib": "rst"} + + visibility = get_config(section="visibility", base="tool.yardang.sphinx-rust") + assert visibility == "pub" finally: os.chdir(original_cwd) def test_sphinx_rust_config_defaults(self, tmp_path): - """Test that sphinx-rust configuration has sensible defaults when not specified.""" + """Test that sphinxcontrib-rust configuration has sensible defaults when not specified.""" pyproject_content = """ [project] name = "test-project" @@ -540,13 +544,13 @@ def test_sphinx_rust_config_defaults(self, tmp_path): rust_crates = get_config(section="crates", base="tool.yardang.sphinx-rust") assert rust_crates is None - doc_formats = get_config(section="doc-formats", base="tool.yardang.sphinx-rust") - assert doc_formats is None + doc_dir = get_config(section="doc-dir", base="tool.yardang.sphinx-rust") + assert doc_dir is None finally: os.chdir(original_cwd) def test_generate_docs_with_sphinx_rust_config(self, tmp_path): - """Test that generate_docs_configuration includes sphinx-rust settings.""" + """Test that generate_docs_configuration includes sphinxcontrib-rust settings.""" pyproject_content = """ [project] name = "test-project" @@ -558,9 +562,9 @@ def test_generate_docs_with_sphinx_rust_config(self, tmp_path): use-autoapi = false [tool.yardang.sphinx-rust] -crates = ["crates/mylib"] -doc-formats = { "mylib" = "markdown" } -viewcode = true +crates = { mylib = "crates/mylib" } +doc-dir = "api" +rustdoc-fmt = "md" """ pyproject_path = tmp_path / "pyproject.toml" pyproject_path.write_text(pyproject_content) @@ -578,12 +582,12 @@ def test_generate_docs_with_sphinx_rust_config(self, tmp_path): conf_path = Path(conf_dir) / "conf.py" conf_content = conf_path.read_text() - # Verify sphinx-rust is enabled + # Verify sphinxcontrib-rust is enabled assert "use_sphinx_rust = True" in conf_content - assert 'extensions.append("sphinx_rust")' in conf_content + assert 'extensions.append("sphinxcontrib_rust")' in conf_content assert "rust_crates = " in conf_content - assert "rust_doc_formats = " in conf_content - assert "rust_viewcode = True" in conf_content + assert 'rust_doc_dir = "api"' in conf_content + assert 'rust_rustdoc_fmt = "md"' in conf_content finally: os.chdir(original_cwd)