pydocstring
A zero-dependency Rust parser for Python docstrings (Google / NumPy style).
Produces a unified syntax tree with byte-precise source locations on every token — designed as infrastructure for linters and formatters.
Python bindings are also available as pydocstring-rs.
Features
- Full syntax tree — builds a complete AST, not just extracted fields; traverse it with the built-in
Visitor+walk_tree - Typed nodes per style — style-specific accessors like
GoogleArg,NumPyParameterwith full type safety - Byte-precise source locations — every token carries its exact byte range for pinpoint diagnostics
- Zero dependencies — pure Rust, no external crates, no regex
- Error-resilient — never panics; malformed input still yields a best-effort tree
- Style auto-detection — hand it a docstring, get back
Style::Google,Style::NumPy, orStyle::Plain - Pattern matching & rewriting — find and
replaceconstructs with$NAME/$$$NAMEpatterns, substituting byte-exact captured content so everything you don't rewrite is preserved
Installation
[]
= "0.3.1"
Usage
Parsing
Parse with auto-detection and traverse the style-independent views — one code path for every docstring style:
use SectionKind;
use ;
let input = "Summary.\n\nArgs:\n x (int): The value.\n y (int): Another value.";
let parsed = parse;
let doc = new;
println!;
for section in doc.sections
When you know the style (or want to force it), the per-style parsers expose style-specific typed wrappers:
use ;
let input = "Summary.\n\nArgs:\n x (int): The value.\n y (int): Another value.";
let result = parse_google;
let doc = cast.unwrap;
for section in doc.sections
NumPy style works the same way — use parse_numpy / NumPyDocstring instead.
Style Auto-Detection
use ;
assert_eq!;
assert_eq!;
assert_eq!;
Style::Plain covers docstrings with no recognised section markers: summary-only,
summary + extended summary, and unrecognised styles such as Sphinx.
Unified Auto-Detecting Parser
Use parse() to let the library detect the style and parse in one step:
use ;
use SyntaxKind;
let result = parse;
assert_eq!;
assert_eq!;
let result = parse;
assert_eq!;
Source Locations
Every token carries byte offsets for precise diagnostics:
use ;
let result = parse_google;
let doc = cast.unwrap;
for section in doc.sections
Syntax Tree
The parse result is a tree of SyntaxNode (branches) and SyntaxToken (leaves), each tagged with a SyntaxKind. Use pretty_print() to visualize:
use parse_google;
let result = parse_google;
println!;
DOCUMENT@0..42 {
SUMMARY: "Summary."@0..8
SECTION@10..42 {
SECTION_HEADER@10..15 {
NAME: "Args"@10..14
COLON: ":"@14..15
}
ENTRY@20..42 {
NAME: "x"@20..21
OPEN_BRACKET: "("@22..23
TYPE: "int"@23..26
CLOSE_BRACKET: ")"@26..27
COLON: ":"@27..28
DESCRIPTION: "The value."@29..39
}
}
}
Visitor Pattern
Walk the tree with the Visitor trait for style-agnostic analysis:
use ;
use parse_google;
let result = parse_google;
let mut collector = NameCollector ;
walk_tree;
assert_eq!;
Style-Independent Model (IR)
Convert any parsed docstring into a style-independent intermediate representation for analysis or transformation:
use ;
let parsed = parse_google;
let doc = to_model.unwrap;
assert_eq!;
for section in &doc.sections
Emitting (Code Generation)
Re-emit a Docstring model in any style — useful for style conversion or formatting.
Google, NumPy, and Sphinx (reStructuredText) output are supported:
use ;
use EmitOptions;
use emit_google;
use emit_numpy;
use emit_sphinx;
let doc = Docstring ;
let options = default;
let google = emit_google;
assert!;
let numpy = emit_numpy;
assert!;
let sphinx = emit_sphinx;
assert!;
assert!;
// Indent the output for embedding in a Python file:
let indented = emit_google;
assert!;
Note: Sphinx support is emit-only.
detect_stylereports Sphinx docstrings asStyle::Plain, so parsing them yields a summary/extended-summary only.
Combine parsing and emitting to convert between styles:
use ;
use EmitOptions;
use emit_numpy;
let parsed = parse_google;
let doc = to_model.unwrap;
let numpy_text = emit_numpy;
assert!;
Pattern Matching & Rewriting
Find constructs with a $NAME / $$$NAME pattern and rewrite them with a
template. Captured metavariables substitute the original source bytes, so
everything you don't explicitly rewrite is preserved byte-for-byte — the issue
The issue #26 use case of annotating one entry without reflowing the rest:
use ;
use Pattern;
let src = "Summary.\n\nArgs:\n x (int): The value.\n y (str): Kept.\n";
let parsed = parse;
let pattern = new.unwrap;
let out = parsed.replace.unwrap;
assert_eq!;
In Python the same lives on the docstring wrappers as doc.replace(pattern, template)
and doc.findall(pattern):
=
=
Supported Sections
Both styles support the following section categories. Typed accessor methods are available on each style's section node.
| Category | NumPy | |
|---|---|---|
| Parameters | args() → GoogleArg |
parameters() → NumPyParameter |
| Returns | returns() → GoogleReturns |
returns() → NumPyReturns |
| Yields | yields() → GoogleYields |
yields() → NumPyYields |
| Raises | exceptions() → GoogleException |
exceptions() → NumPyException |
| Warns | warnings() → GoogleWarning |
warnings() → NumPyWarning |
| See Also | see_also_items() → GoogleSeeAlsoItem |
see_also_items() → NumPySeeAlsoItem |
| Attributes | attributes() → GoogleAttribute |
attributes() → NumPyAttribute |
| Methods | methods() → GoogleMethod |
methods() → NumPyMethod |
| Free text (Notes, Examples, etc.) | body_text() |
body_text() |
Root-level accessors: summary(), extended_summary(), and deprecation() (Google and NumPy). PlainDocstring exposes only summary() and extended_summary().
Development
Common tasks are wrapped in a justfile — run just to list them:
Or invoke cargo directly: