1use std::fmt::{self, Display};
2use std::path::{Path, PathBuf};
3use std::string::ToString;
4
5use serde::de::{self, Deserializer, Error as SerdeError, Visitor};
6use serde::Deserialize;
7
8pub type CompilationDatabase = Vec<CompileCommand>;
10
11#[derive(Debug, Clone, Hash, Eq, PartialEq)]
15pub enum SourceFile {
16 All,
17 File(PathBuf),
18}
19
20impl<'de> Deserialize<'de> for SourceFile {
21 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
22 where
23 D: Deserializer<'de>,
24 {
25 #[allow(dead_code)]
26 struct SourceFileVisitor;
27
28 impl<'de> Visitor<'de> for SourceFileVisitor {
29 type Value = SourceFile;
30
31 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
32 formatter.write_str("a string representing a file path")
33 }
34
35 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
36 where
37 E: SerdeError,
38 {
39 Ok(SourceFile::File(PathBuf::from(value)))
40 }
41 }
42
43 match serde_json::Value::deserialize(deserializer)? {
44 serde_json::Value::String(s) => Ok(SourceFile::File(PathBuf::from(s))),
45 _ => Err(SerdeError::custom("expected a string")),
46 }
47 }
48}
49
50#[derive(Debug, Clone, Hash, Eq, PartialEq)]
56pub enum CompileArgs {
57 Arguments(Vec<String>),
58 Flags(Vec<String>),
59}
60
61impl<'de> Deserialize<'de> for CompileArgs {
62 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
63 where
64 D: Deserializer<'de>,
65 {
66 #[allow(dead_code)]
67 struct CompileArgVisitor;
68
69 impl<'de> Visitor<'de> for CompileArgVisitor {
70 type Value = CompileArgs;
71
72 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
73 formatter.write_str("a string representing a command line argument")
74 }
75
76 fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
77 where
78 A: de::SeqAccess<'de>,
79 {
80 let mut args = Vec::new();
81
82 while let Some(arg) = seq.next_element::<String>()? {
83 args.push(arg);
84 }
85
86 Ok(CompileArgs::Arguments(args))
87 }
88 }
89
90 deserializer.deserialize_seq(CompileArgVisitor)
91 }
92}
93
94#[derive(Debug, Clone, Deserialize)]
100pub struct CompileCommand {
101 pub directory: PathBuf,
104 pub file: SourceFile,
109 pub arguments: Option<CompileArgs>,
114 pub command: Option<String>,
118 pub output: Option<PathBuf>,
122}
123
124impl Display for CompileCommand {
125 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
126 writeln!(f, "{{ \"directory\": \"{}\",", self.directory.display())?;
127
128 match &self.arguments {
129 Some(CompileArgs::Arguments(arguments)) => {
130 write!(f, "\"arguments\": [")?;
131 if arguments.is_empty() {
132 writeln!(f, "],")?;
133 } else {
134 for arg in arguments.iter().take(arguments.len() - 1) {
135 writeln!(f, "\"{arg}\", ")?;
136 }
137 writeln!(f, "\"{}\"],", arguments[arguments.len() - 1])?;
138 }
139 }
140 Some(CompileArgs::Flags(flags)) => {
141 write!(f, "\"flags\": [")?;
142 if flags.is_empty() {
143 writeln!(f, "],")?;
144 } else {
145 for flag in flags.iter().take(flags.len() - 1) {
146 writeln!(f, "\"{flag}\", ")?;
147 }
148 writeln!(f, "\"{}\"],", flags[flags.len() - 1])?;
149 }
150 }
151 None => {}
152 }
153
154 if let Some(command) = &self.command {
155 write!(f, "\"command\": \"{command}\"")?;
156 }
157
158 if let Some(output) = &self.output {
159 writeln!(f, "\"output\": \"{}\"", output.display())?;
160 }
161
162 match &self.file {
163 SourceFile::All => write!(f, "\"file\": all }}")?,
164 SourceFile::File(file) => write!(f, "\"file\": \"{}\" }}", file.display())?,
165 }
166
167 Ok(())
168 }
169}
170
171impl CompileCommand {
172 pub fn args_from_cmd(&self) -> Option<Vec<String>> {
177 let escaped = if let Some(ref cmd) = self.command {
178 cmd.trim().replace("\\\\", "\\").replace("\\\"", "\"")
181 } else {
182 return None;
183 };
184
185 let mut args = Vec::new();
186 let mut start: usize = 0;
187 let mut end: usize = 0;
188 let mut in_quotes = false;
189
190 for c in escaped.chars() {
191 if c == '"' {
192 in_quotes = !in_quotes;
193 end += 1;
194 } else if c.is_whitespace() && !in_quotes && start != end {
195 args.push(escaped[start..end].to_string());
196 end += 1;
197 start = end;
198 } else {
199 end += 1;
200 }
201 }
202
203 if start != end {
204 args.push(escaped[start..end].to_string());
205 }
206
207 Some(args)
208 }
209}
210
211#[must_use]
220pub fn from_compile_flags_txt(directory: &Path, contents: &str) -> CompilationDatabase {
221 let args = CompileArgs::Flags(contents.lines().map(ToString::to_string).collect());
222 vec![CompileCommand {
223 directory: directory.to_path_buf(),
224 file: SourceFile::All,
225 arguments: Some(args),
226 command: None,
227 output: None,
228 }]
229}
230
231#[cfg(test)]
232mod tests {
233 use super::*;
234
235 fn test_args_from_cmd(comp_cmd: &CompileCommand, expected_args: &Vec<&str>) {
236 let translated_args = comp_cmd.args_from_cmd().unwrap();
237
238 assert!(expected_args.len() == translated_args.len());
239 for (expected, actual) in expected_args.iter().zip(translated_args.iter()) {
240 assert!(expected == actual);
241 }
242 }
243
244 #[test]
245 fn it_translates_args_from_empty_cmd() {
246 let comp_cmd = CompileCommand {
247 directory: PathBuf::new(),
248 file: SourceFile::All,
249 arguments: None,
250 command: Some(String::from("")),
251 output: None,
252 };
253
254 let expected_args: Vec<&str> = Vec::new();
255 test_args_from_cmd(&comp_cmd, &expected_args);
256 }
257
258 #[test]
259 fn it_translates_args_from_cmd_1() {
260 let comp_cmd = CompileCommand {
261 directory: PathBuf::new(),
262 file: SourceFile::All,
263 arguments: None,
264 command: Some(String::from(
265 r#"/usr/bin/clang++ -Irelative -DSOMEDEF=\"With spaces, quotes and \\-es.\" -c -o file.o file.cc"#,
266 )),
267 output: None,
268 };
269
270 let expected_args: Vec<&str> = vec![
271 "/usr/bin/clang++",
272 "-Irelative",
273 r#"-DSOMEDEF="With spaces, quotes and \-es.""#,
274 "-c",
275 "-o",
276 "file.o",
277 "file.cc",
278 ];
279 test_args_from_cmd(&comp_cmd, &expected_args);
280 }
281}