lutra_compiler/
discover.rs1use std::collections::HashMap;
2use std::ffi::OsStr;
3use std::path::{Path, PathBuf};
4use std::{fs, io};
5
6use crate::error::Error;
7use crate::project;
8
9#[cfg_attr(feature = "clap", derive(clap::Parser))]
10#[derive(Clone)]
11pub struct DiscoverParams {
12 #[cfg_attr(feature = "clap", arg(long))]
14 pub project: Option<PathBuf>,
15}
16
17pub fn discover(params: DiscoverParams) -> Result<project::SourceTree, Error> {
18 let Some(a_project_file) = params.project else {
19 return Ok(project::SourceTree::empty());
20 };
21
22 tracing::debug!("searching for project root");
24 let mut root_file = a_project_file.canonicalize()?;
25 if root_file.is_dir() {
26 root_file.push("module.lt");
27 }
28 let mut loaded_files = HashMap::new();
29 loop {
30 let file_contents =
31 fs::read_to_string(&root_file).map_err(|io| Error::CannotReadSourceFile {
32 file: root_file.clone(),
33 io,
34 })?;
35
36 let is_submodule = crate::parser::is_submodule(&file_contents).unwrap_or(false);
37
38 loaded_files.insert(root_file.clone(), file_contents);
39 if is_submodule {
40 root_file = parent_module(&root_file).ok_or(Error::CannotFindProjectRoot)?;
41 } else {
42 break;
43 }
44 }
45
46 let root = if root_file.ends_with("module.lt") {
47 root_file.parent().unwrap().to_path_buf()
48 } else {
49 root_file.clone()
50 };
51 tracing::debug!("project root: {}", root.display());
52 let mut project = project::SourceTree::new([], root.clone());
53
54 tracing::debug!("loading project files");
56 let target_extension = Some(OsStr::new("lt"));
57 let mut paths_to_load = vec![root_file];
58 while let Some(path) = paths_to_load.pop() {
59 tracing::debug!(" path: {}", path.display());
60 let content = if let Some(c) = loaded_files.remove(&path) {
61 c
63 } else {
64 let content = match fs::read_to_string(&path) {
66 Ok(c) => c,
67 Err(e) if path.ends_with("module.lt") && e.kind() == io::ErrorKind::NotFound => {
68 continue;
70 }
71 Err(e) => {
72 return Err(Error::CannotReadSourceFile {
73 file: path.to_path_buf(),
74 io: e,
75 });
76 }
77 };
78
79 let is_submodule = crate::parser::is_submodule(&content).unwrap_or(true);
81 if !is_submodule {
82 continue;
83 }
84 content
85 };
86
87 let relative_path = project.get_relative_path(&path).unwrap().to_path_buf();
88 project.insert(relative_path, content);
89
90 if path.ends_with("module.lt") {
92 let Some(dir_path) = path.parent() else {
93 continue;
94 };
95 tracing::debug!(" reading dir: {}", dir_path.display());
96 let mut new_paths = Vec::new();
97 for entry in fs::read_dir(dir_path)? {
98 let entry = entry?;
99 let entry_path = entry.path();
100 let metadata = entry.metadata()?;
101
102 if metadata.is_dir() {
103 new_paths.push(entry.path().join("module.lt"));
104 } else if metadata.is_file()
105 && entry.file_name() != "module.lt" && entry_path.extension() == target_extension
107 {
108 new_paths.push(entry.path());
109 }
110 }
111 new_paths.sort();
112 paths_to_load.extend(new_paths);
113 }
114 }
115
116 Ok(project)
117}
118
119fn parent_module(path: &Path) -> Option<PathBuf> {
120 Some(path.parent()?.join("module.lt"))
121}