Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit a4bb857

Browse files
authored
✨ Add rust_viewcode functionality (#14)
1 parent 1201940 commit a4bb857

File tree

13 files changed

+219
-99
lines changed

13 files changed

+219
-99
lines changed

crates/analyzer/src/analyze/crate_.rs

Lines changed: 29 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -55,45 +55,44 @@ pub fn analyze_crate(path: &str) -> Result<AnalysisResult> {
5555
)));
5656
};
5757

58-
let crate_ = Crate {
59-
name: crate_name,
60-
version: cargo_toml.package.version.clone(),
61-
docstring: "".to_string(),
62-
};
63-
let mut result_ = AnalysisResult {
64-
crate_: crate_.clone(),
58+
let mut result = AnalysisResult {
59+
crate_: Crate {
60+
name: crate_name,
61+
version: cargo_toml.package.version.clone(),
62+
},
6563
modules: vec![],
6664
structs: vec![],
6765
enums: vec![],
6866
};
6967

70-
// read the src/lib directory
71-
let root_file = path.join(to_root);
72-
if !root_file.exists() {
73-
return Ok(result_);
68+
// check existence of the root module
69+
let root_module = path.join(to_root);
70+
if !root_module.exists() {
71+
return Ok(result);
7472
}
7573

7674
// read the top-level module
77-
let content = std::fs::read_to_string(&root_file)?;
78-
let (module, structs, enums) = Module::parse(Some(&root_file), &[&crate_.name], &content)
79-
.context(format!(
75+
let content = std::fs::read_to_string(&root_module)?;
76+
let (module, structs, enums) =
77+
Module::parse(Some(&root_module), &[&result.crate_.name], &content).context(format!(
8078
"Error parsing module {}",
81-
root_file.to_string_lossy()
79+
root_module.to_string_lossy()
8280
))?;
83-
result_.crate_.docstring = module.docstring.clone();
8481
let mut modules_to_read = module
8582
.declarations
8683
.iter()
8784
.map(|s| {
8885
(
89-
root_file.parent().unwrap().to_path_buf(),
86+
root_module.parent().unwrap().to_path_buf(),
9087
s.to_string(),
91-
vec![crate_.name.clone()],
88+
vec![result.crate_.name.clone()],
9289
)
9390
})
9491
.collect::<Vec<_>>();
95-
result_.structs.extend(structs);
96-
result_.enums.extend(enums);
92+
93+
result.modules.push(module);
94+
result.structs.extend(structs);
95+
result.enums.extend(enums);
9796

9897
// recursively find/read the public sub-modules
9998
let mut read_modules = vec![];
@@ -137,12 +136,12 @@ pub fn analyze_crate(path: &str) -> Result<AnalysisResult> {
137136
.map(|s| (submodule_dir.clone(), s.to_string(), path.clone()))
138137
.collect::<Vec<_>>(),
139138
);
140-
result_.modules.push(module);
141-
result_.structs.extend(structs);
142-
result_.enums.extend(enums);
139+
result.modules.push(module);
140+
result.structs.extend(structs);
141+
result.enums.extend(enums);
143142
}
144143

145-
Ok(result_)
144+
Ok(result)
146145
}
147146

148147
#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -164,7 +163,6 @@ pub struct AnalysisResult {
164163
pub struct Crate {
165164
pub name: String,
166165
pub version: String,
167-
pub docstring: String,
168166
}
169167

170168
#[derive(Debug, Deserialize)]
@@ -272,8 +270,13 @@ mod tests {
272270
crate_:
273271
name: my_crate
274272
version: 0.1.0
275-
docstring: The crate docstring
276273
modules:
274+
- file: ~
275+
path:
276+
- my_crate
277+
docstring: The crate docstring
278+
declarations:
279+
- my_module
277280
- file: ~
278281
path:
279282
- my_crate

crates/py_binding/src/lib.rs

Lines changed: 35 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -186,53 +186,62 @@ pub fn load_crate(cache_path: &str, name: &str) -> PyResult<Option<Crate>> {
186186

187187
#[pyfunction]
188188
/// load a module from the cache, if it exists
189-
pub fn load_module(cache_path: &str, name: &str) -> PyResult<Option<Module>> {
189+
pub fn load_module(cache_path: &str, full_name: &str) -> PyResult<Option<Module>> {
190190
let path = std::path::Path::new(cache_path)
191191
.join("modules")
192-
.join(format!("{}.json", name));
192+
.join(format!("{}.json", full_name));
193193
if !path.exists() {
194194
return Ok(None);
195195
}
196196
let contents = read_file(&path)?;
197-
let mod_: analyze::Module = deserialize_object(name, &contents)?;
197+
let mod_: analyze::Module = deserialize_object(full_name, &contents)?;
198198
Ok(Some(mod_.into()))
199199
}
200200

201201
#[pyfunction]
202202
/// load a struct from the cache, if it exists
203-
pub fn load_struct(cache_path: &str, name: &str) -> PyResult<Option<Struct>> {
203+
pub fn load_struct(cache_path: &str, full_name: &str) -> PyResult<Option<Struct>> {
204204
let path = std::path::Path::new(cache_path)
205205
.join("structs")
206-
.join(format!("{}.json", name));
206+
.join(format!("{}.json", full_name));
207207
if !path.exists() {
208208
return Ok(None);
209209
}
210210
let contents = read_file(&path)?;
211-
let struct_: analyze::Struct = deserialize_object(name, &contents)?;
211+
let struct_: analyze::Struct = deserialize_object(full_name, &contents)?;
212212
Ok(Some(struct_.into()))
213213
}
214214

215215
#[pyfunction]
216216
/// load an enum from the cache, if it exists
217-
pub fn load_enum(cache_path: &str, name: &str) -> PyResult<Option<Enum>> {
217+
pub fn load_enum(cache_path: &str, full_name: &str) -> PyResult<Option<Enum>> {
218218
let path = std::path::Path::new(cache_path)
219219
.join("enums")
220-
.join(format!("{}.json", name));
220+
.join(format!("{}.json", full_name));
221221
if !path.exists() {
222222
return Ok(None);
223223
}
224224
let contents = read_file(&path)?;
225-
let enum_: analyze::Enum = deserialize_object(name, &contents)?;
225+
let enum_: analyze::Enum = deserialize_object(full_name, &contents)?;
226226
Ok(Some(enum_.into()))
227227
}
228228

229229
#[pyfunction]
230-
/// load all modules from the cache that begin with the given prefix
231-
pub fn load_modules(cache_path: &str, prefix: &str) -> PyResult<Vec<Module>> {
230+
/// load all modules from the cache that have a common ancestor
231+
pub fn load_modules(
232+
cache_path: &str,
233+
ancestor: Vec<String>,
234+
include_self: bool,
235+
) -> PyResult<Vec<Module>> {
232236
let path = std::path::Path::new(cache_path).join("modules");
233237
if !path.exists() {
234238
return Ok(vec![]);
235239
}
240+
let ancestor_name = ancestor.join("::");
241+
let mut prefix = ancestor.join("::");
242+
if !prefix.is_empty() {
243+
prefix.push_str("::");
244+
}
236245
let mut modules = vec![];
237246
for entry in std::fs::read_dir(path)? {
238247
let entry = entry?;
@@ -246,7 +255,7 @@ pub fn load_modules(cache_path: &str, prefix: &str) -> PyResult<Vec<Module>> {
246255
Some(name) => name,
247256
None => continue,
248257
};
249-
if !name.starts_with(prefix) {
258+
if !(name.starts_with(&prefix) || (include_self && name == &ancestor_name)) {
250259
continue;
251260
}
252261
let contents = read_file(&path)?;
@@ -258,12 +267,16 @@ pub fn load_modules(cache_path: &str, prefix: &str) -> PyResult<Vec<Module>> {
258267
}
259268

260269
#[pyfunction]
261-
/// load all structs from the cache that begin with the given prefix
262-
pub fn load_structs(cache_path: &str, prefix: &str) -> PyResult<Vec<Struct>> {
270+
/// load all structs from the cache that have a common ancestor
271+
pub fn load_structs(cache_path: &str, ancestor: Vec<String>) -> PyResult<Vec<Struct>> {
263272
let path = std::path::Path::new(cache_path).join("structs");
264273
if !path.exists() {
265274
return Ok(vec![]);
266275
}
276+
let mut prefix = ancestor.join("::");
277+
if !prefix.is_empty() {
278+
prefix.push_str("::");
279+
}
267280
let mut structs = vec![];
268281
for entry in std::fs::read_dir(path)? {
269282
let entry = entry?;
@@ -277,7 +290,7 @@ pub fn load_structs(cache_path: &str, prefix: &str) -> PyResult<Vec<Struct>> {
277290
Some(name) => name,
278291
None => continue,
279292
};
280-
if !name.starts_with(prefix) {
293+
if !name.starts_with(&prefix) {
281294
continue;
282295
}
283296
let contents = read_file(&path)?;
@@ -289,12 +302,16 @@ pub fn load_structs(cache_path: &str, prefix: &str) -> PyResult<Vec<Struct>> {
289302
}
290303

291304
#[pyfunction]
292-
/// load all enums from the cache that begin with the given prefix
293-
pub fn load_enums(cache_path: &str, prefix: &str) -> PyResult<Vec<Enum>> {
305+
/// load all enums from the cache that that have a common ancestor
306+
pub fn load_enums(cache_path: &str, ancestor: Vec<String>) -> PyResult<Vec<Enum>> {
294307
let path = std::path::Path::new(cache_path).join("enums");
295308
if !path.exists() {
296309
return Ok(vec![]);
297310
}
311+
let mut prefix = ancestor.join("::");
312+
if !prefix.is_empty() {
313+
prefix.push_str("::");
314+
}
298315
let mut enums = vec![];
299316
for entry in std::fs::read_dir(path)? {
300317
let entry = entry?;
@@ -308,7 +325,7 @@ pub fn load_enums(cache_path: &str, prefix: &str) -> PyResult<Vec<Enum>> {
308325
Some(name) => name,
309326
None => continue,
310327
};
311-
if !name.starts_with(prefix) {
328+
if !name.starts_with(&prefix) {
312329
continue;
313330
}
314331
let contents = read_file(&path)?;

crates/py_binding/src/objects.rs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@ pub struct Crate {
1010
pub name: String,
1111
#[pyo3(get)]
1212
pub version: String,
13-
#[pyo3(get)]
14-
pub docstring: String,
1513
}
1614

1715
#[pymethods]
@@ -34,7 +32,6 @@ impl From<analyze::Crate> for Crate {
3432
Crate {
3533
name: crate_.name,
3634
version: crate_.version,
37-
docstring: crate_.docstring,
3835
}
3936
}
4037
}

docs/_static/custom.css

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,6 @@
55
table.need {
66
background-color: var(--color-code-background);
77
}
8+
tr.need.content {
9+
padding-left: 0;
10+
}

docs/conf.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
"analyzer": "restructuredtext",
2323
"sphinx_rust": "markdown",
2424
}
25+
rust_viewcode = True
2526

2627
intersphinx_mapping = {
2728
"sphinx": ("https://www.sphinx-doc.org", None),

docs/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ Add `sphinx_rust` to your `conf.py`, and specifiy the paths to the Rust crates y
4646
"../path/to/crate",
4747
...
4848
]
49+
rust_viewcode = True # Optional: add "View Source" links
4950
...
5051
5152
Now add a `toctree` in your `index.rst`, to point towards the generated documentation for each crate

python/sphinx_rust/config.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,20 @@ class RustConfig:
1313

1414
rust_crates: list[str]
1515
rust_doc_formats: dict[str, str]
16+
rust_viewcode: bool
1617

1718
@classmethod
1819
def from_app(cls, app: Sphinx) -> RustConfig:
1920
"""Create a new RustConfig from the Sphinx application."""
2021
return cls(
2122
rust_crates=app.config.rust_crates,
2223
rust_doc_formats=app.config.rust_doc_formats,
24+
rust_viewcode=app.config.rust_viewcode,
2325
)
2426

2527
@staticmethod
2628
def add_configs(app: Sphinx) -> None:
2729
"""Add the configuration values for the Rust domain."""
2830
app.add_config_value("rust_crates", [], "env")
2931
app.add_config_value("rust_doc_formats", {}, "env")
32+
app.add_config_value("rust_viewcode", False, "env")

python/sphinx_rust/directives/_core.py

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ class RustAutoDirective(SphinxDirective):
3333
def doc(self) -> nodes.document:
3434
return self.state.document # type: ignore[no-any-return]
3535

36+
@property
37+
def rust_config(self) -> RustConfig:
38+
return RustConfig.from_app(self.env.app)
39+
3640
@property
3741
def rust_domain(self) -> RustDomain:
3842
# avoid circular import
@@ -157,10 +161,10 @@ def parse_docstring(
157161
return document.children
158162

159163

160-
def create_xref(
161-
docname: str, path: str, objtype: ObjType, *, warn_dangling: bool = False
164+
def create_object_xref(
165+
docname: str, full_name: str, objtype: ObjType, *, warn_dangling: bool = False
162166
) -> addnodes.pending_xref:
163-
"""Create a cross-reference node.
167+
"""Create a cross-reference node to a rust object.
164168
165169
:param docname: The document name.
166170
:param path: The fully qualified path to the object, e.g. ``crate::module::Item``.
@@ -171,15 +175,42 @@ def create_xref(
171175
"reftype": objtype,
172176
"refexplicit": True,
173177
"refwarn": warn_dangling,
174-
"reftarget": path,
178+
"reftarget": full_name,
175179
}
176-
ref = addnodes.pending_xref(path, **options)
177-
name = path.split("::")[-1]
180+
ref = addnodes.pending_xref(full_name, **options)
181+
name = full_name.split("::")[-1]
178182
ref += nodes.literal(name, name)
179183

180184
return ref
181185

182186

187+
def create_source_xref(
188+
docname: str,
189+
full_name: str,
190+
*,
191+
warn_dangling: bool = False,
192+
text: str | None = None,
193+
) -> addnodes.pending_xref:
194+
"""Create a cross-reference node to the source-code of a rust object.
195+
196+
:param docname: The document name.
197+
:param path: The fully qualified path to the object, e.g. ``crate::module::Item``.
198+
"""
199+
options = {
200+
"refdoc": docname,
201+
"refdomain": "std",
202+
"reftype": "ref",
203+
"refexplicit": True,
204+
"refwarn": warn_dangling,
205+
"reftarget": f"rust-code:{full_name}",
206+
}
207+
ref = addnodes.pending_xref(full_name, **options)
208+
text = full_name.split("::")[-1] if text is None else text
209+
ref += nodes.literal(text, text)
210+
211+
return ref
212+
213+
183214
def type_segs_to_nodes(segs: list[TypeSegment]) -> list[nodes.Node]:
184215
"""Convert a list of type segments to nodes."""
185216
nodes_: list[nodes.Node] = []

0 commit comments

Comments
 (0)