sphinx_rustdocgen/directives/
struct_directive.rs

1// sphinxcontrib_rust - Sphinx extension for the Rust programming language
2// Copyright (C) 2024  Munir Contractor
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program.  If not, see <https://www.gnu.org/licenses/>.
16
17//! Implementation of the ``rust:struct`` directive
18
19use syn::{Fields, Generics, ItemStruct, ItemUnion, Variant, Visibility};
20
21use crate::directives::directive_options::{DirectiveOption, DirectiveVisibility, IndexEntryType};
22use crate::directives::variable_directive::VariableDirective;
23use crate::directives::{extract_doc_from_attrs, Directive, ImplDirective};
24use crate::formats::{MdContent, MdDirective, RstContent, RstDirective};
25use crate::nodes::{nodes_for_generics, nodes_for_type, nodes_for_where_clause, Node};
26
27/// Struct to hold details for documenting a struct or a union.
28#[derive(Clone, Debug)]
29pub struct StructDirective {
30    /// The full Rust path of the struct, used as the name of the directive.
31    pub(crate) name: String,
32    /// The identifier for the struct.
33    pub(crate) ident: String,
34    /// The directive options to use.
35    pub(crate) options: Vec<DirectiveOption>,
36    /// The docstring for the struct.
37    pub(crate) content: Vec<String>,
38    /// The fields of the struct, named or unnamed.
39    pub(crate) fields: Vec<VariableDirective>,
40    /// Items within impl blocks for the struct/union.
41    pub(crate) self_impls: Vec<ImplDirective>,
42    /// Trait impls for the struct/union.
43    pub(crate) trait_impls: Vec<ImplDirective>,
44}
45
46/// DRY macro to create the nodes for the directive's layout.
47macro_rules! make_nodes {
48    ($ident:expr, $fields:expr, $generics:expr, $item_keyword:expr) => {{
49        let mut nodes = if let Some(keyword) = $item_keyword {
50            vec![
51                Node::Keyword(keyword),
52                Node::Space,
53                Node::Name($ident.to_string()),
54            ]
55        }
56        else {
57            vec![Node::Name($ident.to_string())]
58        };
59
60        if let Some(generics) = $generics {
61            nodes.extend(nodes_for_generics(generics));
62        }
63
64        if let Fields::Unnamed(fields) = &$fields {
65            nodes.push(Node::Punctuation("("));
66            for field in &fields.unnamed {
67                nodes.extend(nodes_for_type(&field.ty));
68                nodes.push(Node::Punctuation(", "));
69            }
70            nodes.pop();
71            nodes.push(Node::Punctuation(")"));
72        }
73
74        if let Some(generics) = $generics {
75            if let Some(wc) = &generics.where_clause {
76                nodes.extend(nodes_for_where_clause(wc));
77            }
78        }
79
80        nodes
81    }};
82}
83
84impl StructDirective {
85    const DIRECTIVE_NAME: &'static str = "struct";
86
87    /// Create a struct directive for an enum variant.
88    pub(crate) fn from_variant(
89        parent_path: &str,
90        variant: &Variant,
91        inherited_visibility: &Option<&Visibility>,
92    ) -> StructDirective {
93        let name = format!("{}::{}", parent_path, variant.ident);
94
95        let options = vec![
96            DirectiveOption::Index(IndexEntryType::SubEntry),
97            DirectiveOption::Vis(DirectiveVisibility::from(inherited_visibility.unwrap())),
98            DirectiveOption::Toc(variant.ident.to_string()),
99            DirectiveOption::Layout(make_nodes!(
100                variant.ident,
101                variant.fields,
102                None::<&Generics>,
103                None
104            )),
105        ];
106
107        let fields = VariableDirective::from_fields(
108            &name,
109            &variant.fields,
110            inherited_visibility,
111            IndexEntryType::None,
112        );
113
114        StructDirective {
115            name,
116            ident: variant.ident.to_string(),
117            options,
118            content: extract_doc_from_attrs(&variant.attrs),
119            fields,
120            self_impls: vec![],
121            trait_impls: vec![],
122        }
123    }
124
125    /// Create a struct directive for a struct.
126    pub(crate) fn from_item(parent_path: &str, item: &ItemStruct) -> Directive {
127        let name = format!("{}::{}", parent_path, item.ident);
128
129        let options = vec![
130            DirectiveOption::Index(IndexEntryType::WithSubEntries),
131            DirectiveOption::Vis(DirectiveVisibility::from(&item.vis)),
132            DirectiveOption::Toc(format!("struct {}", &item.ident)),
133            DirectiveOption::Layout(make_nodes!(
134                item.ident,
135                item.fields,
136                Some(&item.generics),
137                Some(Self::DIRECTIVE_NAME)
138            )),
139        ];
140
141        let fields =
142            VariableDirective::from_fields(&name, &item.fields, &None, IndexEntryType::SubEntry);
143
144        Directive::Struct(StructDirective {
145            name,
146            ident: item.ident.to_string(),
147            options,
148            content: extract_doc_from_attrs(&item.attrs),
149            fields,
150            self_impls: vec![],
151            trait_impls: vec![],
152        })
153    }
154
155    /// Create a struct directive for a union.
156    pub(crate) fn from_union(parent_path: &str, item: &ItemUnion) -> Directive {
157        let name = format!("{parent_path}::{}", item.ident);
158        let fields = Fields::Named(item.fields.clone());
159
160        let options = vec![
161            DirectiveOption::Index(IndexEntryType::WithSubEntries),
162            DirectiveOption::Vis(DirectiveVisibility::from(&item.vis)),
163            DirectiveOption::Toc(format!("union {}", item.ident)),
164            DirectiveOption::Layout(make_nodes!(
165                item.ident,
166                fields,
167                Some(&item.generics),
168                Some("union")
169            )),
170        ];
171
172        let fields =
173            VariableDirective::from_fields(&name, &fields, &None, IndexEntryType::SubEntry);
174
175        Directive::Struct(StructDirective {
176            name,
177            ident: item.ident.to_string(),
178            options,
179            content: extract_doc_from_attrs(&item.attrs),
180            fields,
181            self_impls: vec![],
182            trait_impls: vec![],
183        })
184    }
185
186    /// Return the visibility of this directive.
187    pub(crate) fn directive_visibility(&self) -> &DirectiveVisibility {
188        if let DirectiveOption::Vis(v) = &self.options[1] {
189            return v;
190        }
191        unreachable!("Struct: order of options changed")
192    }
193
194    /// Change the parent module of the struct and its items.
195    pub(crate) fn change_parent(&mut self, new_parent: &str) {
196        self.name = format!("{new_parent}::{}", self.ident);
197        for field in &mut self.fields {
198            field.change_parent(&self.name);
199        }
200        for impl_ in &mut self.self_impls {
201            impl_.change_parent(new_parent);
202        }
203        for impl_ in &mut self.trait_impls {
204            impl_.change_parent(new_parent);
205        }
206    }
207
208    /// Add the impl directive to the struct.
209    ///
210    /// The parent and visibility of the impl directive are updated along with
211    /// the ownership.
212    ///
213    /// Args:
214    ///     :impl\_: The :rust:struct:`ImplDirective` for the struct.
215    // noinspection DuplicatedCode
216    pub(crate) fn add_impl(&mut self, mut impl_: ImplDirective) {
217        // Set parent to the struct's parent for proper naming.
218        impl_.change_parent(&self.name[0..self.name.rfind("::").unwrap()]);
219        impl_.set_directive_visibility(self.directive_visibility());
220        if impl_.trait_.is_some() {
221            self.trait_impls.push(impl_);
222        }
223        else {
224            self.self_impls.push(impl_);
225        }
226    }
227}
228
229impl RstDirective for StructDirective {
230    fn get_rst_text(self, level: usize, max_visibility: &DirectiveVisibility) -> Vec<String> {
231        if self.directive_visibility() > max_visibility {
232            return vec![];
233        }
234        let content_indent = Self::make_content_indent(level);
235
236        let mut text =
237            Self::make_rst_header(Self::DIRECTIVE_NAME, &self.name, &self.options, level);
238        text.extend(self.content.get_rst_text(&content_indent));
239
240        for field in self.fields {
241            text.extend(field.get_rst_text(level + 1, max_visibility));
242        }
243
244        text.extend(Self::make_rst_section(
245            "Implementations",
246            level,
247            self.self_impls.into_iter().map(Directive::Impl).collect(),
248            max_visibility,
249        ));
250
251        text.extend(Self::make_rst_section(
252            "Traits implemented",
253            level,
254            self.trait_impls.into_iter().map(Directive::Impl).collect(),
255            max_visibility,
256        ));
257
258        text
259    }
260}
261
262impl MdDirective for StructDirective {
263    fn get_md_text(self, fence_size: usize, max_visibility: &DirectiveVisibility) -> Vec<String> {
264        if self.directive_visibility() > max_visibility {
265            return vec![];
266        }
267        let fence = Self::make_fence(fence_size);
268
269        let mut text =
270            Self::make_md_header(Self::DIRECTIVE_NAME, &self.name, &self.options, &fence);
271        text.extend(self.content.get_md_text());
272
273        for field in self.fields {
274            text.extend(field.get_md_text(fence_size - 1, max_visibility));
275        }
276
277        text.extend(Self::make_md_section(
278            "Implementations",
279            fence_size,
280            self.self_impls.into_iter().map(Directive::Impl).collect(),
281            max_visibility,
282        ));
283
284        text.extend(Self::make_md_section(
285            "Traits implemented",
286            fence_size,
287            self.trait_impls.into_iter().map(Directive::Impl).collect(),
288            max_visibility,
289        ));
290
291        text.push(fence);
292        text
293    }
294
295    // noinspection DuplicatedCode
296    fn fence_size(&self) -> usize {
297        [
298            match self.fields.iter().map(VariableDirective::fence_size).max() {
299                Some(s) => s + 1,
300                None => 3,
301            },
302            match self.trait_impls.iter().map(ImplDirective::fence_size).max() {
303                Some(s) => s + 1,
304                None => 3,
305            },
306            match self.self_impls.iter().map(ImplDirective::fence_size).max() {
307                Some(s) => s + 1,
308                None => 3,
309            },
310        ]
311        .into_iter()
312        .max()
313        .unwrap()
314    }
315}