sofie_model/
declarations.rs1use 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 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}