A fast, zero-dependency Rust parser for Python docstrings with full AST and source location tracking.
- Zero external dependencies — pure Rust implementation
- Docstring styles:
- Google style — fully supported
- NumPy style — fully supported
- Accurate source spans (byte offsets) on every AST node
- Diagnostic-based error reporting — partial results + diagnostics, never panics
- Style auto-detection — automatically identifies NumPy or Google style
- Comprehensive test coverage (140+ tests)
Add to your Cargo.toml:
[dependencies]
pydocstring = "0.1.0"use pydocstring::numpy::parse_numpy;
let docstring = r#"
Calculate the area of a rectangle.
This function takes the width and height of a rectangle
and returns its area.
Parameters
----------
width : float
The width of the rectangle.
height : float
The height of the rectangle.
Returns
-------
float
The area of the rectangle.
Raises
------
ValueError
If width or height is negative.
"#;
let result = parse_numpy(docstring);
println!("Summary: {}", result.summary.source_text(&result.source));
for item in &result.items {
if let pydocstring::NumPyDocstringItem::Section(s) = item {
if let pydocstring::NumPySectionBody::Parameters(params) = &s.body {
for param in params {
let names: Vec<&str> = param.names.iter()
.map(|n| n.source_text(&result.source)).collect();
println!(" {:?}: {:?}", names,
param.r#type.as_ref().map(|t| t.source_text(&result.source)));
}
}
}
}use pydocstring::google::parse_google;
let docstring = r#"
Calculate the area of a rectangle.
This function takes the width and height of a rectangle
and returns its area.
Args:
width (float): The width of the rectangle.
height (float): The height of the rectangle.
Returns:
float: The area of the rectangle.
Raises:
ValueError: If width or height is negative.
"#;
let result = parse_google(docstring);
println!("Summary: {}", result.summary.source_text(&result.source));
for item in &result.items {
if let pydocstring::GoogleDocstringItem::Section(s) = item {
if let pydocstring::GoogleSectionBody::Args(args) = &s.body {
for arg in args {
println!(" {} ({:?}): {}", arg.name.source_text(&result.source),
arg.r#type.as_ref().map(|t| t.source_text(&result.source)),
arg.description.source_text(&result.source));
}
}
}
}use pydocstring::{detect_style, Style};
let numpy_doc = "Summary.\n\nParameters\n----------\nx : int\n Desc.";
assert_eq!(detect_style(numpy_doc), Style::NumPy);
let google_doc = "Summary.\n\nArgs:\n x: Desc.";
assert_eq!(detect_style(google_doc), Style::Google);Parsers always return a result — even for malformed input. Diagnostics are collected alongside the best-effort AST:
use pydocstring::google::parse_google;
let result = parse_google("Summary.\n\nArgs:\n : missing name");
if result.has_errors() {
for diag in result.errors() {
eprintln!("{}", diag); // e.g. "error at 14..28: ..."
}
}
// The AST is still available
println!("Summary: {}", result.summary.source_text(&result.source));Every AST node carries a TextRange (byte offsets) so linters can report precise positions:
use pydocstring::numpy::parse_numpy;
use pydocstring::LineIndex;
let docstring = "Summary.\n\nParameters\n----------\nx : int\n Desc.";
let result = parse_numpy(docstring);
for item in &result.items {
if let pydocstring::NumPyDocstringItem::Section(s) = item {
if let pydocstring::NumPySectionBody::Parameters(params) = &s.body {
if let Some(param) = params.first() {
let name_range = ¶m.names[0];
println!("Parameter '{}' at byte {}..{}",
name_range.source_text(&result.source),
name_range.start(), name_range.end());
// Convert to line/column if needed
let index = LineIndex::from_source(docstring);
let (line, col) = index.line_col(name_range.start());
println!(" line {}, col {}", line, col);
}
}
}
}| Section | Method | Return Type |
|---|---|---|
| Parameters | parameters() |
Vec<&NumPyParameter> |
| Other Parameters | other_parameters() |
Vec<&NumPyParameter> |
| Returns | returns() |
Vec<&NumPyReturns> |
| Yields | yields() |
Vec<&NumPyReturns> |
| Receives | receives() |
Vec<&NumPyParameter> |
| Raises | raises() |
Vec<&NumPyException> |
| Warns | warns() |
Vec<&NumPyWarning> |
| Warnings | warnings() |
Option<&TextRange> |
| See Also | see_also() |
Vec<&SeeAlsoItem> |
| Notes | notes() |
Option<&TextRange> |
| References | references() |
Vec<&NumPyReference> |
| Examples | examples() |
Option<&TextRange> |
| Attributes | attributes() |
Vec<&NumPyAttribute> |
| Methods | methods() |
Vec<&NumPyMethod> |
Additionally, NumPyDocstring has fields: summary, deprecation, extended_summary.
| Section | Method | Return Type |
|---|---|---|
| Args | args() |
Vec<&GoogleArgument> |
| Keyword Args | keyword_args() |
Vec<&GoogleArgument> |
| Other Parameters | other_parameters() |
Vec<&GoogleArgument> |
| Returns | returns() |
Vec<&GoogleReturns> |
| Yields | yields() |
Vec<&GoogleReturns> |
| Receives | receives() |
Vec<&GoogleArgument> |
| Raises | raises() |
Vec<&GoogleException> |
| Warns | warns() |
Vec<&GoogleWarning> |
| Warnings | warnings() |
Option<&TextRange> |
| See Also | see_also() |
Vec<&GoogleSeeAlsoItem> |
| Notes | notes() |
Option<&TextRange> |
| References | references() |
Option<&TextRange> |
| Examples | examples() |
Option<&TextRange> |
| Attributes | attributes() |
Vec<&GoogleAttribute> |
| Methods | methods() |
Vec<&GoogleMethod> |
| Todo | todo() |
Option<&TextRange> |
Additionally, GoogleDocstring has fields: summary, description, and admonition sections (Attention, Caution, Danger, etc.).
# Build
cargo build
# Run tests
cargo test
# Run examples
cargo run --example parse_numpy
cargo run --example parse_googleMIT