pydocstring 0.4.0

A zero-dependency Rust parser for Python docstrings (Google and NumPy styles) with a unified syntax tree and byte-precise source locations
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
//! Style-independent document model (IR) for docstrings.
//!
//! This module provides owned, editable data structures that represent the
//! semantic content of a docstring without being tied to any particular style
//! (Google, NumPy, etc.) or to source text positions.
//!
//! # Usage
//!
//! ```rust
//! use pydocstring::parse::parse;
//!
//! let doc = parse("Summary.\n\nArgs:\n    x (int): The value.\n").to_model();
//! assert_eq!(doc.summary.as_deref(), Some("Summary."));
//! ```

// =============================================================================
// Docstring (root)
// =============================================================================

/// Style-independent representation of a parsed docstring.
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct Docstring {
    /// Brief one-line summary.
    pub summary: Option<String>,
    /// Extended description (may span multiple lines).
    pub extended_summary: Option<String>,
    /// Document-level rST directives (`.. name:: argument`), in source
    /// order. The only directive the parsers produce today is
    /// `.. deprecated::`; see [`Docstring::deprecation`] for the shorthand.
    pub directives: Vec<Directive>,
    /// Ordered list of sections.
    pub sections: Vec<Section>,
}

impl Docstring {
    /// The deprecation notice, if present: the first directive named
    /// `deprecated` (its [`Directive::argument`] is the version).
    pub fn deprecation(&self) -> Option<&Directive> {
        self.directives.iter().find(|d| d.name == "deprecated")
    }
}

// =============================================================================
// Section
// =============================================================================

/// A single section within a docstring.
///
/// A section is a [`SectionKind`] paired with a flat sequence of [`Block`]s in
/// source order. Replacing the pre-0.4 role-keyed `enum Section`
/// (`Parameters(Vec<Parameter>)`, …), this completes the style-independent
/// unification: `kind` identifies the section and `blocks` carries its body
/// without baking the role into the shape.
///
/// The representation is permissive — nothing statically prevents a
/// [`Block::Parameter`] under a `Raises` section — consistent with the CST's
/// "permissive structure + documented interpretation" line; emitters render
/// every block totally.
///
/// `#[non_exhaustive]`: build one with [`Section::new`] or one of the
/// role-named constructors, not a struct literal, so that a future field is
/// not a breaking change. The rest of the model IR is deliberately *not*
/// `#[non_exhaustive]` — [`Docstring`], [`Parameter`] and the other entry
/// types are values you construct to feed [`emit`](crate::emit), and sealing
/// them would take struct-literal construction away with nothing to replace
/// it.
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct Section {
    /// The style-independent kind of this section.
    pub kind: SectionKind,
    /// The section body: a flat sequence of blocks in source order.
    pub blocks: Vec<Block>,
}

impl Section {
    /// Construct a section from its kind and block sequence.
    pub fn new(kind: SectionKind, blocks: Vec<Block>) -> Self {
        Self { kind, blocks }
    }

    /// A `Parameters` section wrapping each parameter in a [`Block::Parameter`].
    pub fn parameters(params: Vec<Parameter>) -> Self {
        Self::new(
            SectionKind::Parameters,
            params.into_iter().map(Block::Parameter).collect(),
        )
    }

    /// A `Keyword Parameters` section (Google `Keyword Args`).
    pub fn keyword_parameters(params: Vec<Parameter>) -> Self {
        Self::new(
            SectionKind::KeywordParameters,
            params.into_iter().map(Block::Parameter).collect(),
        )
    }

    /// An `Other Parameters` section.
    pub fn other_parameters(params: Vec<Parameter>) -> Self {
        Self::new(
            SectionKind::OtherParameters,
            params.into_iter().map(Block::Parameter).collect(),
        )
    }

    /// A `Receives` section.
    pub fn receives(params: Vec<Parameter>) -> Self {
        Self::new(
            SectionKind::Receives,
            params.into_iter().map(Block::Parameter).collect(),
        )
    }

    /// A `Returns` section wrapping each entry in a [`Block::Return`].
    pub fn returns(returns: Vec<Return>) -> Self {
        Self::new(SectionKind::Returns, returns.into_iter().map(Block::Return).collect())
    }

    /// A `Yields` section.
    pub fn yields(returns: Vec<Return>) -> Self {
        Self::new(SectionKind::Yields, returns.into_iter().map(Block::Return).collect())
    }

    /// A `Raises` section wrapping each entry in a [`Block::Exception`].
    pub fn raises(entries: Vec<ExceptionEntry>) -> Self {
        Self::new(SectionKind::Raises, entries.into_iter().map(Block::Exception).collect())
    }

    /// A `Warns` section.
    pub fn warns(entries: Vec<ExceptionEntry>) -> Self {
        Self::new(SectionKind::Warns, entries.into_iter().map(Block::Exception).collect())
    }

    /// An `Attributes` section wrapping each entry in a [`Block::Attribute`].
    pub fn attributes(attrs: Vec<Attribute>) -> Self {
        Self::new(
            SectionKind::Attributes,
            attrs.into_iter().map(Block::Attribute).collect(),
        )
    }

    /// A `Methods` section wrapping each entry in a [`Block::Method`].
    pub fn methods(methods: Vec<Method>) -> Self {
        Self::new(SectionKind::Methods, methods.into_iter().map(Block::Method).collect())
    }

    /// A `See Also` section wrapping each entry in a [`Block::SeeAlso`].
    pub fn see_also(items: Vec<SeeAlsoEntry>) -> Self {
        Self::new(SectionKind::SeeAlso, items.into_iter().map(Block::SeeAlso).collect())
    }

    /// A `References` section wrapping each entry in a [`Block::Reference`].
    pub fn references(refs: Vec<Reference>) -> Self {
        Self::new(
            SectionKind::References,
            refs.into_iter().map(Block::Reference).collect(),
        )
    }

    /// A free-text section (Notes, Examples, …) whose body is a single
    /// [`Block::Paragraph`]. (The full dissolve of free-text bodies into
    /// multiple blocks is deferred to Phase 4 increment E.)
    pub fn free_text(kind: FreeSectionKind, body: String) -> Self {
        Self::new(SectionKind::FreeText(kind), vec![Block::Paragraph(body)])
    }
}

/// A single body block within a [`Section`], in source order.
///
/// A structured section body is a flat run of blocks: prose [`Block::Paragraph`]s
/// interleaved with typed entries. Prose is a *model-layer* notion (#104): the
/// CST keeps every base-indent body line as an `ENTRY` (the predictable
/// napoleon/numpydoc line grammar), and `to_model` decides which bare entries
/// read as paragraphs.
///
/// `#[non_exhaustive]`: new block shapes (e.g. reST fields, literal/doctest
/// blocks) may be added in minor releases, so downstream `match`es need a
/// wildcard arm.
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum Block {
    /// A prose paragraph (a section intro, a between-entries note, or a
    /// free-text section body).
    Paragraph(String),
    /// A parameter / argument entry.
    Parameter(Parameter),
    /// A return / yield entry.
    Return(Return),
    /// An exception / warning entry.
    Exception(ExceptionEntry),
    /// An attribute entry.
    Attribute(Attribute),
    /// A method entry.
    Method(Method),
    /// A see-also entry.
    SeeAlso(SeeAlsoEntry),
    /// A reference / citation entry.
    Reference(Reference),
}

impl Block {
    /// The [`Parameter`] if this is a [`Block::Parameter`].
    pub fn as_parameter(&self) -> Option<&Parameter> {
        match self {
            Block::Parameter(p) => Some(p),
            _ => None,
        }
    }

    /// The [`Return`] if this is a [`Block::Return`].
    pub fn as_return(&self) -> Option<&Return> {
        match self {
            Block::Return(r) => Some(r),
            _ => None,
        }
    }

    /// The [`ExceptionEntry`] if this is a [`Block::Exception`].
    pub fn as_exception(&self) -> Option<&ExceptionEntry> {
        match self {
            Block::Exception(e) => Some(e),
            _ => None,
        }
    }

    /// The [`Attribute`] if this is a [`Block::Attribute`].
    pub fn as_attribute(&self) -> Option<&Attribute> {
        match self {
            Block::Attribute(a) => Some(a),
            _ => None,
        }
    }

    /// The [`Method`] if this is a [`Block::Method`].
    pub fn as_method(&self) -> Option<&Method> {
        match self {
            Block::Method(m) => Some(m),
            _ => None,
        }
    }

    /// The [`SeeAlsoEntry`] if this is a [`Block::SeeAlso`].
    pub fn as_see_also(&self) -> Option<&SeeAlsoEntry> {
        match self {
            Block::SeeAlso(s) => Some(s),
            _ => None,
        }
    }

    /// The [`Reference`] if this is a [`Block::Reference`].
    pub fn as_reference(&self) -> Option<&Reference> {
        match self {
            Block::Reference(r) => Some(r),
            _ => None,
        }
    }

    /// The prose text if this is a [`Block::Paragraph`].
    pub fn as_paragraph(&self) -> Option<&str> {
        match self {
            Block::Paragraph(p) => Some(p),
            _ => None,
        }
    }
}

// =============================================================================
// SectionKind
// =============================================================================

/// Unified section kind identifier (style-independent).
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum SectionKind {
    /// `Args` / `Parameters`
    Parameters,
    /// `Keyword Args` (Google only)
    KeywordParameters,
    /// `Other Parameters`
    OtherParameters,
    /// `Receives`
    Receives,
    /// `Returns`
    Returns,
    /// `Yields`
    Yields,
    /// `Raises`
    Raises,
    /// `Warns`
    Warns,
    /// `Attributes`
    Attributes,
    /// `Methods`
    Methods,
    /// `See Also`
    SeeAlso,
    /// `References`
    References,
    /// Free-text section
    FreeText(FreeSectionKind),
}

// =============================================================================
// FreeSectionKind
// =============================================================================

/// Kind of a free-text (non-structured) section.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum FreeSectionKind {
    /// `Notes`
    Notes,
    /// `Examples`
    Examples,
    /// `Warnings`
    Warnings,
    /// `Todo` (Google only)
    Todo,
    /// `Attention` (Google only)
    Attention,
    /// `Caution` (Google only)
    Caution,
    /// `Danger` (Google only)
    Danger,
    /// `Error` (Google only)
    Error,
    /// `Hint` (Google only)
    Hint,
    /// `Important` (Google only)
    Important,
    /// `Tip` (Google only)
    Tip,
    /// Unrecognised section name.
    Unknown(String),
}

// =============================================================================
// Entry types
// =============================================================================

/// A parameter / argument entry.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Parameter {
    /// Parameter name(s). NumPy supports multiple names (`x, y`); Google always has one.
    pub names: Vec<String>,
    /// Type annotation (e.g. `int`, `Dict[str, Any]`).
    pub type_annotation: Option<String>,
    /// Description text (may be multi-line).
    pub description: Option<String>,
    /// Whether the parameter is marked `optional`.
    pub is_optional: bool,
    /// Default value text (NumPy `default: value` syntax).
    pub default_value: Option<String>,
}

/// A return / yield entry.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Return {
    /// Return name (NumPy supports named returns; Google does not).
    pub name: Option<String>,
    /// Type annotation.
    pub type_annotation: Option<String>,
    /// Description text.
    pub description: Option<String>,
}

/// An exception or warning entry (for Raises / Warns sections).
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ExceptionEntry {
    /// Exception / warning type name (e.g. `ValueError`).
    pub type_name: String,
    /// Description text.
    pub description: Option<String>,
}

/// A see-also item.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SeeAlsoEntry {
    /// Referenced names (can be multiple, comma-separated).
    pub names: Vec<String>,
    /// Description text.
    pub description: Option<String>,
}

/// A reference entry (NumPy `.. [1] ...` style).
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Reference {
    /// Citation label (`1`, `CIT2002`, `#f1`, … — the text inside
    /// `.. [label]`). Renamed from `number` in 0.3.0: labels are not always
    /// numeric.
    pub label: Option<String>,
    /// Reference content text.
    pub content: Option<String>,
}

/// An attribute entry.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Attribute {
    /// Attribute name(s). NumPy supports multiple names (`jac, hess`), like
    /// [`Parameter::names`]; Google always has one. Renamed from `name` in
    /// 0.3.0: keeping only the first name dropped the rest (#89).
    pub names: Vec<String>,
    /// Type annotation.
    pub type_annotation: Option<String>,
    /// Description text.
    pub description: Option<String>,
}

/// A method entry.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Method {
    /// Method name.
    pub name: String,
    /// Type / signature info (Google puts this in brackets).
    pub type_annotation: Option<String>,
    /// Description text.
    pub description: Option<String>,
}

/// A document-level rST directive (`.. name:: argument` + indented body).
///
/// Generalizes the pre-0.3.0 `Deprecation` struct (which this replaces): a
/// deprecation notice is a `Directive` with `name == "deprecated"` whose
/// `argument` is the version. See [`Docstring::deprecation`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Directive {
    /// Directive name (e.g. `"deprecated"`).
    pub name: String,
    /// Directive argument (e.g. the version of a `.. deprecated::`).
    pub argument: Option<String>,
    /// Directive body / description text.
    pub description: Option<String>,
}