Skip to content
Open
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
9 changes: 4 additions & 5 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.9"
python-version: "3.10"
- uses: pre-commit/[email protected]

test-cargo:
Expand All @@ -49,16 +49,15 @@ jobs:
matrix:
os: [ubuntu-latest]
python-version:
- '3.9'
- '3.10'
- '3.11'
- '3.12'
- 'pypy3.9'
- 'pypy3.10'
include:
- os: windows-latest
python-version: '3.9'
python-version: '3.10'
- os: macos-latest
python-version: '3.9'
python-version: '3.10'

runs-on: ${{ matrix.os }}

Expand Down
5 changes: 3 additions & 2 deletions .readthedocs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@
version: 2

sphinx:
configuration: docs/conf.py
builder: html

build:
os: "ubuntu-22.04"
tools:
python: "3.9"
rust: "1.75"
python: "3.10"
rust: "1.78"

python:
install:
Expand Down
1 change: 1 addition & 0 deletions crates/analyzer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ serde.workspace = true
serde_json.workspace = true
syn.workspace = true
toml.workspace = true
cargo_metadata = "0.18"

[dev-dependencies]
insta.workspace = true
Expand Down
115 changes: 44 additions & 71 deletions crates/analyzer/src/analyze/crate_.rs
Original file line number Diff line number Diff line change
@@ -1,68 +1,65 @@
//! Analyze the crate
use anyhow::{Context, Result};
use anyhow::{anyhow, Context, Result};
use cargo_metadata::{MetadataCommand, Target};
use serde::{Deserialize, Serialize};
use std::path::PathBuf;

use crate::data_model::{Crate, Enum, Function, Module, Struct};

pub fn analyze_crate(path: &str) -> Result<AnalysisResult> {
// make the path absolute
// TODO we use dunce to canonicalize the path because otherwise there is issues with python's os.path.relpath on windows, but maybe we should fix this on the Python side
let path =
dunce::canonicalize(path).context(format!("Error resolving crate path: {}", path))?;
let crate_dir =
dunce::canonicalize(path).context(format!("Error resolving crate path: {path}"))?;
eprintln!("running new analyzer");
// check the path is a directory
if !path.is_dir() {
return Err(anyhow::anyhow!(format!(
if !crate_dir.is_dir() {
return Err(anyhow!(
"Crate path is not a directory: {}",
path.to_string_lossy()
)));
crate_dir.to_string_lossy()
));
}
// check if Cargo.toml exists
let cargo_toml_path = path.join("Cargo.toml");
let cargo_toml_path = crate_dir.join("Cargo.toml");
if !cargo_toml_path.exists() {
return Err(anyhow::anyhow!(format!(
return Err(anyhow!(
"Cargo.toml does not exist in: {}",
path.to_string_lossy()
)));
crate_dir.to_string_lossy()
));
}

// read the Cargo.toml and initialize the Crate struct
let contents = std::fs::read_to_string(&cargo_toml_path)?;
let cargo_toml: CargoToml = toml::from_str(&contents).context(format!(
"Error parsing: {}",
cargo_toml_path.to_string_lossy()
))?;
// use `cargo_metadata` instead of implementing own TOML parser
let metadata = MetadataCommand::new()
.manifest_path(&cargo_toml_path)
.exec()
.context("Failed to run `cargo metadata`")?;

// check whether the crate is a library or binary
let (crate_name, to_root) = if let Some(lib) = cargo_toml.lib {
if cargo_toml.bin.is_some() {
return Err(anyhow::anyhow!(format!(
"Both lib and bin sections in: {}",
path.to_string_lossy()
)));
}
(
lib.name.unwrap_or(cargo_toml.package.name),
lib.path.unwrap_or("src/lib.rs".to_string()),
)
} else if let Some(bin) = cargo_toml.bin {
(
bin.name.unwrap_or(cargo_toml.package.name),
bin.path.unwrap_or("src/main.rs".to_string()),
)
} else {
return Err(anyhow::anyhow!(format!(
"No lib or bin section in: {}",
path.to_string_lossy()
)));
};
let root_pkg = metadata
.root_package()
.ok_or_else(|| anyhow!("`cargo metadata` returned no root package"))?;

// Prefer library target; fall back to the first binary target
let root_target: &Target = root_pkg
.targets
.iter()
.find(|t| t.kind.contains(&"lib".into()))
.or_else(|| {
root_pkg
.targets
.iter()
.find(|t| t.kind.contains(&"bin".into()))
})
.ok_or_else(|| anyhow!("No lib or bin target defined in manifest"))?;

let crate_name = root_target.name.clone();
let root_module = PathBuf::from(&root_target.src_path);

let mut result = AnalysisResult::new(Crate {
name: crate_name,
version: cargo_toml.package.version.clone(),
name: crate_name.clone(),
version: root_pkg.version.to_string(), // workspace-aware
});

// check existence of the root module
let root_module = path.join(to_root);
if !root_module.exists() {
return Ok(result);
}
Expand All @@ -74,6 +71,7 @@ pub fn analyze_crate(path: &str) -> Result<AnalysisResult> {
"Error parsing module {}",
root_module.to_string_lossy()
))?;

let mut modules_to_read = module
.declarations
.iter()
Expand All @@ -91,7 +89,7 @@ pub fn analyze_crate(path: &str) -> Result<AnalysisResult> {
result.enums.extend(enums);
result.functions.extend(functions);

// recursively find/read the public sub-modules
// recursively find/read the public submodules
let mut read_modules = vec![];
while let Some((parent_dir, module_name, parent)) = modules_to_read.pop() {
let (module_path, submodule_dir) =
Expand Down Expand Up @@ -126,12 +124,12 @@ pub fn analyze_crate(path: &str) -> Result<AnalysisResult> {
"Error parsing module {}",
module_path.to_string_lossy()
))?;

modules_to_read.extend(
module
.declarations
.iter()
.map(|s| (submodule_dir.clone(), s.to_string(), path.clone()))
.collect::<Vec<_>>(),
.map(|s| (submodule_dir.clone(), s.to_string(), path.clone())),
);
result.modules.push(module);
result.structs.extend(structs);
Expand Down Expand Up @@ -164,31 +162,6 @@ impl AnalysisResult {
}
}

#[derive(Debug, Deserialize)]
struct CargoToml {
package: Package,
bin: Option<Bin>,
lib: Option<Lib>,
}

#[derive(Debug, Deserialize)]
struct Package {
name: String,
version: String,
}

#[derive(Debug, Deserialize)]
struct Lib {
name: Option<String>,
path: Option<String>,
}

#[derive(Debug, Deserialize)]
struct Bin {
name: Option<String>,
path: Option<String>,
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
2 changes: 1 addition & 1 deletion crates/py_binding/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "sphinx_rust"
version = "0.0.2"
version = "0.0.2-dev1"
publish = false
edition = "2021"
authors.workspace = true
Expand Down
23 changes: 8 additions & 15 deletions crates/py_binding/src/data_query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,7 @@ where
Ok(crate_) => crate_,
Err(err) => {
return Err(PyIOError::new_err(format!(
"Could not deserialize {}: {}",
name, err
"Could not deserialize {name}: {err}"
)))
}
};
Expand All @@ -39,7 +38,7 @@ where
pub fn load_crate(cache_path: &str, name: &str) -> PyResult<Option<Crate>> {
let path = std::path::Path::new(cache_path)
.join("crates")
.join(format!("{}.json", name));
.join(format!("{name}.json"));
if !path.exists() {
return Ok(None);
}
Expand All @@ -53,7 +52,7 @@ pub fn load_crate(cache_path: &str, name: &str) -> PyResult<Option<Crate>> {
pub fn load_module(cache_path: &str, full_name: &str) -> PyResult<Option<Module>> {
let path = std::path::Path::new(cache_path)
.join("modules")
.join(format!("{}.json", full_name));
.join(format!("{full_name}.json"));
if !path.exists() {
return Ok(None);
}
Expand All @@ -67,7 +66,7 @@ pub fn load_module(cache_path: &str, full_name: &str) -> PyResult<Option<Module>
pub fn load_struct(cache_path: &str, full_name: &str) -> PyResult<Option<Struct>> {
let path = std::path::Path::new(cache_path)
.join("structs")
.join(format!("{}.json", full_name));
.join(format!("{full_name}.json"));
if !path.exists() {
return Ok(None);
}
Expand All @@ -81,7 +80,7 @@ pub fn load_struct(cache_path: &str, full_name: &str) -> PyResult<Option<Struct>
pub fn load_enum(cache_path: &str, full_name: &str) -> PyResult<Option<Enum>> {
let path = std::path::Path::new(cache_path)
.join("enums")
.join(format!("{}.json", full_name));
.join(format!("{full_name}.json"));
if !path.exists() {
return Ok(None);
}
Expand All @@ -95,7 +94,7 @@ pub fn load_enum(cache_path: &str, full_name: &str) -> PyResult<Option<Enum>> {
pub fn load_function(cache_path: &str, full_name: &str) -> PyResult<Option<Function>> {
let path = std::path::Path::new(cache_path)
.join("functions")
.join(format!("{}.json", full_name));
.join(format!("{full_name}.json"));
if !path.exists() {
return Ok(None);
}
Expand All @@ -106,14 +105,8 @@ pub fn load_function(cache_path: &str, full_name: &str) -> PyResult<Option<Funct

/// Check if a path is a child of a given parent, and return the fully qualified name of the child.
fn is_child(path: &std::path::PathBuf, parent: &Vec<String>) -> Option<String> {
let name = match path.file_stem() {
Some(name) => name,
None => return None,
};
let name = match name.to_str() {
Some(name) => name,
None => return None,
};
let name = path.file_stem()?;
let name = name.to_str()?;
let name_path = name.split("::").collect::<Vec<_>>();
if name_path.len() != parent.len() + 1 {
return None;
Expand Down
15 changes: 5 additions & 10 deletions crates/py_binding/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,25 +162,20 @@ where
Ok(value) => value,
Err(err) => {
return Err(PyIOError::new_err(format!(
"Could not serialize value: {}",
err
"Could not serialize value: {err}"
)))
}
};
if path.exists() {
match std::fs::read_to_string(path) {
Ok(old_value) => {
if value == old_value {
return Ok(());
}
if let Ok(old_value) = std::fs::read_to_string(path) {
if value == old_value {
return Ok(());
}
Err(_) => {}
};
}
match std::fs::write(path, value) {
Err(err) => Err(PyIOError::new_err(format!(
"Could not write value to file: {}",
err
"Could not write value to file: {err}"
))),
Ok(_) => Ok(()),
}
Expand Down
7 changes: 3 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ dynamic = ["version"]
description = "Sphinx plugin for documentation of Rust projects."
authors = [{ name = "Chris Sewell", email = "[email protected]" }]
readme = "README.md"
requires-python = ">=3.9"
requires-python = ">=3.10"
license = { file = "LICENSE" }
keywords = [
"sphinx",
Expand All @@ -26,7 +26,6 @@ classifiers = [
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
"Programming Language :: Python",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
Expand All @@ -37,7 +36,7 @@ classifiers = [
"Topic :: Text Processing :: Markup",
]
dependencies = [
"sphinx~=7.3"
"sphinx>=8,<9"
]

[project.urls]
Expand Down Expand Up @@ -113,7 +112,7 @@ allowlist_externals = bash
commands_pre = bash -c "unset CONDA_PREFIX; maturin develop"
commands = {posargs:ipython}

[testenv:test-{py39,py310,py311,py312}]
[testenv:test-{py310,py311,py312}]
extras = test
passenv = TERM
; ensure that the compilation is up-to-date
Expand Down
9 changes: 6 additions & 3 deletions python/sphinx_rust/directives/_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class RustAutoDirective(SphinxDirective):

@property
def doc(self) -> nodes.document:
return self.state.document # type: ignore[no-any-return]
return self.state.document

@property
def rust_config(self) -> RustConfig:
Expand Down Expand Up @@ -146,8 +146,11 @@ def parse_docstring(
return []

docstring = item.docstring if docstring is None else docstring
source_path = env.doc2path( # TODO this actually should be the rust file path
env.docname

source_path = str(
env.doc2path( # TODO this actually should be the rust file path
env.docname
)
)
# TODO how to handle line numbers?
document = utils.new_document(source_path, doc.settings)
Expand Down
Loading