Skip to main content

sofie_model/
declarations.rs

1use std::path::{Path, PathBuf};
2
3use syn::ext::IdentExt;
4use syn::{Item, ItemMod};
5
6use crate::ModelError;
7use crate::syntax::read_file;
8
9#[derive(Clone, Debug, PartialEq, Eq)]
10pub enum ModuleDeclaration {
11    Declared,
12    Missing {
13        file: PathBuf,
14        /// 1-based line and 0-based column of the inline module's closing brace; end of file when `None`.
15        before: Option<(usize, usize)>,
16        missing: Vec<String>,
17    },
18}
19
20pub fn module_declaration(root: &Path, path: &[&str]) -> Result<ModuleDeclaration, ModelError> {
21    let mut file = root.to_path_buf();
22    let mut module_dir = file.parent().map(Path::to_path_buf).unwrap_or_default();
23    let mut remaining = path;
24    loop {
25        let syntax = read_file(&file)?;
26        match walk(&syntax.items, remaining, &module_dir) {
27            Step::Declared => return Ok(ModuleDeclaration::Declared),
28            Step::Missing { before, rest } => {
29                return Ok(ModuleDeclaration::Missing {
30                    file,
31                    before,
32                    missing: rest.iter().map(|level| (*level).to_owned()).collect(),
33                });
34            }
35            Step::InFile {
36                file: child,
37                directory,
38                rest,
39            } => {
40                file = child;
41                module_dir = directory;
42                remaining = rest;
43            }
44        }
45    }
46}
47
48enum Step<'p> {
49    Declared,
50    Missing {
51        before: Option<(usize, usize)>,
52        rest: &'p [&'p str],
53    },
54    InFile {
55        file: PathBuf,
56        directory: PathBuf,
57        rest: &'p [&'p str],
58    },
59}
60
61fn walk<'p>(items: &[Item], path: &'p [&'p str], module_dir: &Path) -> Step<'p> {
62    let mut items = items;
63    let mut before = None;
64    let mut module_dir = module_dir.to_path_buf();
65    let mut remaining = path;
66    while let Some((first, rest)) = remaining.split_first() {
67        let Some(found) = find_module(items, first) else {
68            return Step::Missing {
69                before,
70                rest: remaining,
71            };
72        };
73        module_dir = module_dir.join(first);
74        if rest.is_empty() {
75            return Step::Declared;
76        }
77        let Some((brace, inner)) = &found.content else {
78            let flat = module_dir.with_extension("rs");
79            let file = if flat.is_file() {
80                flat
81            } else {
82                module_dir.join("mod.rs")
83            };
84            return Step::InFile {
85                file,
86                directory: module_dir,
87                rest,
88            };
89        };
90        let close = brace.span.close().start();
91        before = Some((close.line, close.column));
92        items = inner;
93        remaining = rest;
94    }
95    Step::Declared
96}
97
98fn find_module<'i>(items: &'i [Item], name: &str) -> Option<&'i ItemMod> {
99    items.iter().find_map(|item| match item {
100        Item::Mod(module) if module.ident.unraw() == name => Some(module),
101        _ => None,
102    })
103}