sphinx_rustdocgen/directives/
use_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
17use std::collections::BTreeMap;
18
19use ::syn;
20use syn::{ItemUse, UseTree, Visibility};
21
22use crate::directives::directive_options::DirectiveVisibility;
23use crate::directives::{
24    extract_doc_from_attrs,
25    Directive,
26    MdDirective,
27    ModuleDirective,
28    RstDirective,
29};
30use crate::formats::{MdOption, RstOption};
31
32#[derive(Clone, Debug, Default)]
33pub(crate) struct UsePathBuilder {
34    pub(crate) path: Vec<String>,
35    pub(crate) used_name: String,
36}
37
38/// Options for the use directive.
39enum UseDirectiveOption {
40    UsedName(String),
41    Reexport(String),
42}
43
44impl RstOption for UseDirectiveOption {
45    fn get_rst_text(&self, indent: &str) -> Option<String> {
46        match self {
47            UseDirectiveOption::UsedName(u) => Some(format!("{indent}:used_name: {u}")),
48            UseDirectiveOption::Reexport(r) => Some(format!("{indent}:reexport: {r}")),
49        }
50    }
51}
52
53impl MdOption for UseDirectiveOption {
54    fn get_md_text(&self) -> Option<String> {
55        match self {
56            UseDirectiveOption::UsedName(u) => Some(format!(":used_name: {u}")),
57            UseDirectiveOption::Reexport(r) => Some(format!(":reexport: {r}")),
58        }
59    }
60}
61
62#[derive(Clone, Debug)]
63pub struct UseDirective {
64    pub(crate) paths: BTreeMap<String, String>,
65    pub(crate) reexport: Option<String>,
66    pub(crate) content: Vec<String>,
67    directive_visibility: DirectiveVisibility,
68}
69
70impl UseDirective {
71    const DIRECTIVE_NAME: &'static str = "use";
72
73    /// Create a use directive for a path with the provided target.
74    pub(crate) fn for_path(path: &str, target: &str) -> Self {
75        UseDirective {
76            paths: BTreeMap::from([(target.to_string(), path.to_string())]),
77            reexport: None,
78            content: vec![],
79            directive_visibility: DirectiveVisibility::Pvt,
80        }
81    }
82
83    /// Create a use directive using the provided used name to path map.
84    pub(crate) fn for_use_paths(paths: BTreeMap<String, String>) -> Self {
85        UseDirective {
86            paths,
87            reexport: None,
88            content: vec![],
89            directive_visibility: DirectiveVisibility::Pvt,
90        }
91    }
92
93    /// Create a use directive and wrap it within the
94    /// :rust:struct:`Directive::UseDirective` enum variant.
95    pub(crate) fn from_item_as_directive(parent_path: &str, item: &ItemUse) -> Directive {
96        Directive::Use(Self::from_item(parent_path, item))
97    }
98
99    /// Create a use directive from the AST item parsed by syn.
100    pub(crate) fn from_item(parent_path: &str, item: &ItemUse) -> Self {
101        // This is always the name of the crate being processed since the parent
102        // path is either the crate name or the module path in which the use
103        // statement appears. The module path will always begin with the crate
104        // name.
105        let crate_name = &parent_path[0..parent_path.find("::").unwrap_or(parent_path.len())];
106
107        // Vec to hold use paths that are completely parsed out.
108        let mut complete_paths = vec![];
109
110        // Vec to hold use paths that are still being parsed out.
111        // This is initialized with one empty path.
112        let mut incomplete_paths = vec![UsePathBuilder::default()];
113
114        // Stack of the items identified from the use paths.
115        let mut item_stack = vec![&item.tree];
116
117        while let Some(t) = item_stack.pop() {
118            match t {
119                UseTree::Path(p) => {
120                    // Next ident from the path.
121                    // Add this to the incomplete path at the top of the stack.
122
123                    incomplete_paths
124                        .last_mut()
125                        .unwrap()
126                        .path
127                        .push(p.ident.to_string());
128                    item_stack.push(&p.tree);
129                }
130                UseTree::Name(n) => {
131                    // Imported a name. This completes the use path.
132
133                    let mut use_path = incomplete_paths.pop().unwrap();
134
135                    // Handle use paths that import self
136                    let name = n.ident.to_string();
137                    if name == "self" {
138                        use_path.used_name.clone_from(use_path.path.last().unwrap());
139                    }
140                    else {
141                        use_path.path.push(name.clone());
142                        use_path.used_name = name;
143                    }
144
145                    // Handle use paths that start with crate or self
146                    if use_path.path[0] == "crate" {
147                        use_path.path[0] = crate_name.to_string();
148                    }
149                    else if use_path.path[0] == "self" {
150                        use_path.path[0] = parent_path.to_string();
151                    }
152
153                    complete_paths.push(use_path);
154                }
155                UseTree::Rename(r) => {
156                    // Imported and renamed. This completes the use path.
157
158                    let mut use_path = incomplete_paths.pop().unwrap();
159                    // Handle self imports
160                    let name = r.ident.to_string();
161                    if name != "self" {
162                        use_path.path.push(name);
163                    }
164                    use_path.used_name = r.rename.to_string();
165                    if use_path.path[0] == "crate" {
166                        use_path.path[0] = crate_name.to_string();
167                    }
168                    complete_paths.push(use_path);
169                }
170                UseTree::Glob(_) => {
171                    // Glob import. This completes the use path.
172                    // Unsure what to do with the target here.
173                    // For now, glob imports are just ignored.
174
175                    incomplete_paths.pop();
176                }
177                UseTree::Group(g) => {
178                    // Group of imports within curly braces.
179                    // Create a copy of the current path on the stack for each
180                    // item of the group and add back to the incomplete paths.
181                    // Add all items from the group to the stack. In the next
182                    // iteration of the loop, the last item from the group is
183                    // fetched and processed until it terminates, and then the
184                    // next item from the group is processed.
185
186                    let last = incomplete_paths.pop().unwrap();
187                    for _ in 0..g.items.len() {
188                        incomplete_paths.push(last.clone());
189                    }
190                    for item in &g.items {
191                        item_stack.push(item);
192                    }
193                }
194            }
195        }
196
197        UseDirective {
198            paths: complete_paths
199                .into_iter()
200                .map(|p| (p.used_name, p.path.join("::")))
201                .collect(),
202            reexport: if matches!(&item.vis, Visibility::Inherited) {
203                None
204            }
205            else {
206                Some(parent_path.into())
207            },
208            content: extract_doc_from_attrs(&item.attrs),
209            directive_visibility: DirectiveVisibility::from(&item.vis),
210        }
211    }
212
213    /// Resolve any relative paths using the list of modules provided.
214    ///
215    /// The method checks if any of the use paths are relative paths by
216    /// comparing the first segment with the module's ident. If it matches,
217    /// the path is updated to be an absolute path starting with the crate.
218    pub(crate) fn resolve_relative_paths(&mut self, modules: &Vec<ModuleDirective>) {
219        'path_loop: for path in self.paths.values_mut() {
220            for module in modules {
221                if path.starts_with(&format!("{}::", module.ident)) {
222                    path.insert_str(
223                        0,
224                        &format!("{}::", &module.name[0..module.name.rfind("::").unwrap()]),
225                    );
226                    continue 'path_loop;
227                }
228            }
229        }
230    }
231
232    /// Check whether the directive contains the given path.
233    ///
234    /// This returns true if the ``item_path`` is one of the paths in the
235    /// directive.
236    pub(crate) fn contains(&self, item_path: &str) -> bool {
237        self.paths.iter().any(|(_, path)| path == item_path)
238    }
239
240    /// Find the path for the given ident within the use directive, if any.
241    pub(crate) fn find(&self, ident: &str) -> Option<&String> {
242        self.paths
243            .iter()
244            .find(|&(used_name, _)| used_name == ident)
245            .map(|(_, path)| path)
246    }
247
248    /// Return the visibility of this directive.
249    pub(crate) fn directive_visibility(&self) -> &DirectiveVisibility {
250        &self.directive_visibility
251    }
252
253    /// Inlines an item and returns it as a new ``(used_name, path)`` tuple.
254    ///
255    /// The new path is ``{reexport}::{used_name}`` of the old path. The used
256    /// name for the new path is the same as that of the old path.
257    ///
258    /// Returns:
259    ///     The function returns None if the directive is not a re-export or
260    ///     a path matching the item was not found.
261    pub(crate) fn inline(&mut self, item_name: &str) -> Option<(String, String)> {
262        let mut found = None;
263        if let Some(reexport) = &self.reexport {
264            self.paths.retain(|used_name, path| {
265                if path == item_name {
266                    found = Some((used_name.clone(), format!("{reexport}::{used_name}")));
267                    return false;
268                }
269                true
270            });
271        }
272        found
273    }
274}
275
276impl RstDirective for UseDirective {
277    fn get_rst_text(self, level: usize, _: &DirectiveVisibility) -> Vec<String> {
278        let mut text = vec![];
279        for (used_name, path) in self.paths {
280            let mut options = vec![UseDirectiveOption::UsedName(used_name)];
281            if let Some(reexport) = &self.reexport {
282                options.push(UseDirectiveOption::Reexport(reexport.clone()));
283            }
284            text.extend(Self::make_rst_header(
285                Self::DIRECTIVE_NAME,
286                path,
287                &options,
288                level,
289            ));
290        }
291
292        text
293    }
294}
295
296impl MdDirective for UseDirective {
297    fn get_md_text(self, fence_size: usize, _: &DirectiveVisibility) -> Vec<String> {
298        let fence = Self::make_fence(fence_size);
299        let mut text = vec![];
300        for (used_name, path) in self.paths {
301            let mut options = vec![UseDirectiveOption::UsedName(used_name)];
302            if let Some(reexport) = &self.reexport {
303                options.push(UseDirectiveOption::Reexport(reexport.clone()));
304            }
305            text.extend(Self::make_md_header(
306                Self::DIRECTIVE_NAME,
307                path,
308                &options,
309                &fence,
310            ));
311            text.push(fence.clone());
312        }
313
314        text
315    }
316}