include_graph/dependencies/
cparse.rs1use super::error::Error;
2
3use regex::Regex;
4use std::{
5 fmt::Debug,
6 path::{Path, PathBuf},
7 sync::Arc,
8};
9use tokio::{
10 fs::File,
11 io::{AsyncBufReadExt as _, BufReader},
12 task::JoinSet,
13};
14use tracing::{error, info, trace};
15
16fn try_resolve(head: &Path, tail: &PathBuf) -> Option<PathBuf> {
19 head.join(tail).canonicalize().ok()
20}
21
22#[derive(PartialEq, Eq, Hash, PartialOrd, Ord, Debug)]
23pub enum FileType {
24 Header,
25 Source,
26 Unknown,
27}
28
29impl FileType {
30 pub fn of(path: &Path) -> Self {
31 let ext = path
32 .extension()
33 .and_then(|e| e.to_str())
34 .unwrap_or("")
35 .to_lowercase();
36 match ext.as_str() {
37 "h" | "hpp" => FileType::Header,
38 "c" | "cpp" | "cc" | "cxx" => FileType::Source,
39 _ => FileType::Unknown,
40 }
41 }
42}
43
44pub async fn extract_includes(
48 path: &PathBuf,
49 include_dirs: &[PathBuf],
50) -> Result<Vec<PathBuf>, Error> {
51 let f = File::open(path).await.map_err(|source| Error::IOError {
52 source,
53 path: path.clone(),
54 message: "open",
55 })?;
56
57 let reader = BufReader::new(f);
58
59 let inc_re = Regex::new(r##"^\s*#include\s*(["<])([^">]*)[">]"##).unwrap();
60
61 let mut result = Vec::new();
62 let parent_dir = PathBuf::from(path.parent().unwrap());
63
64 let mut lines = reader.lines();
65
66 loop {
67 let line = lines.next_line().await.map_err(|source| Error::IOError {
68 source,
69 path: path.clone(),
70 message: "line read",
71 })?;
72
73 let line = match line {
74 Some(value) => value,
75 None => break,
76 };
77
78 if let Some(captures) = inc_re.captures(&line) {
79 let inc_type = captures.get(1).unwrap().as_str();
80 let relative_path = PathBuf::from(captures.get(2).unwrap().as_str());
81
82 trace!("Possible include: {:?}", relative_path);
83
84 if inc_type == "\"" {
85 if let Some(p) = try_resolve(&parent_dir, &relative_path) {
86 result.push(p);
87 continue;
88 }
89 }
90
91 if let Some(p) = include_dirs
92 .iter()
93 .find_map(|i| try_resolve(i, &relative_path))
94 {
95 result.push(p);
96 } else {
97 trace!("Include {:?} could not be resolved", relative_path);
101 }
102 }
103 }
104
105 info!(target: "include-extract",
106 "Includes for:\n {:?}: {:#?}", path, result);
107
108 Ok(result)
109}
110
111#[derive(Debug, PartialEq, PartialOrd)]
112pub struct SourceWithIncludes {
113 pub path: PathBuf,
114 pub includes: Vec<PathBuf>,
115}
116
117pub async fn all_sources_and_includes<I, E>(
119 paths: I,
120 includes: &[PathBuf],
121) -> Result<Vec<SourceWithIncludes>, Error>
122where
123 I: Iterator<Item = Result<PathBuf, E>>,
124 E: Debug,
125{
126 let includes = Arc::new(Vec::from(includes));
127
128 let mut join_set = JoinSet::new();
129
130 for entry in paths {
131 let path = match entry {
132 Ok(value) => value.canonicalize().map_err(|e| Error::Internal {
133 message: format!("{:?}", e),
134 })?,
135 Err(e) => {
136 return Err(Error::Internal {
137 message: format!("{:?}", e),
138 })
139 }
140 };
141
142 if FileType::of(&path) == FileType::Unknown {
143 trace!("Skipping non-source: {:?}", path);
144 continue;
145 }
146
147 let includes = includes.clone();
149
150 join_set.spawn(async move {
151 trace!("PROCESS: {:?}", path);
152 let includes = match extract_includes(&path, &includes).await {
153 Ok(value) => value,
154 Err(e) => {
155 error!("Error extracing includes: {:?}", e);
156 return Err(e);
157 }
158 };
159
160 Ok(SourceWithIncludes { path, includes })
161 });
162 }
163
164 let mut results = Vec::new();
165 while let Some(h) = join_set.join_next().await {
166 let r = h.map_err(Error::JoinError)?;
167 results.push(r?)
168 }
169
170 Ok(results)
171}