use std::fmt::{self, Display};
use std::path::PathBuf;
use serde::de::{Deserializer, Error as SerdeError, Visitor};
use serde::Deserialize;
pub type CompilationDatabase = Vec<CompileCommand>;
#[derive(Debug, Clone)]
enum SourceFile {
All,
File(PathBuf),
}
impl<'de> Deserialize<'de> for SourceFile {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct SourceFileVisitor;
impl<'de> Visitor<'de> for SourceFileVisitor {
type Value = SourceFile;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a string representing a file path")
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: SerdeError,
{
Ok(SourceFile::File(PathBuf::from(value)))
}
}
match serde_json::Value::deserialize(deserializer)? {
serde_json::Value::String(s) => Ok(SourceFile::File(PathBuf::from(s))),
_ => Err(SerdeError::custom("expected a string")),
}
}
}
#[derive(Debug, Clone, Deserialize)]
pub struct CompileCommand {
directory: PathBuf,
file: SourceFile,
arguments: Option<Vec<String>>,
command: Option<String>,
output: Option<PathBuf>,
}
impl Display for CompileCommand {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "{{ \"directory\": \"{}\",", self.directory.display())?;
if let Some(arguments) = &self.arguments {
write!(f, "\"arguments\": [")?;
if arguments.is_empty() {
writeln!(f, "],")?;
} else {
for arg in arguments.iter().take(arguments.len() - 1) {
writeln!(f, "\"{}\", ", arg)?;
}
writeln!(f, "\"{}\"],", arguments[arguments.len() - 1])?;
}
}
if let Some(command) = &self.command {
write!(f, "\"command\": \"{}\"", command)?;
}
if let Some(output) = &self.output {
writeln!(f, "\"output\": \"{}\"", output.display())?;
}
match &self.file {
SourceFile::All => write!(f, "\"file\": all }}")?,
SourceFile::File(file) => write!(f, "\"file\": \"{}\" }}", file.display())?,
}
Ok(())
}
}
#[must_use]
pub fn from_compile_flags_txt(directory: &PathBuf, contents: &str) -> CompilationDatabase {
let args = contents.lines().map(|line| line.to_string()).collect();
vec![CompileCommand {
directory: directory.clone(),
file: SourceFile::All,
arguments: Some(args),
command: None,
output: None,
}]
}