#![deny(missing_docs)]
use std::env;
use std::io::Write;
use std::process::{Command, Stdio};
use thiserror::Error;
#[derive(Debug, PartialEq)]
#[non_exhaustive]
pub enum ClangFormatStyle {
Chromium,
Default,
File,
GNU,
Google,
Llvm,
Microsoft,
Mozilla,
WebKit,
Custom(String),
}
impl ClangFormatStyle {
fn as_str(&self) -> &str {
match self {
Self::Chromium => "Chromium",
Self::Default => "{}",
Self::File => "file",
Self::GNU => "GNU",
Self::Google => "Google",
Self::Llvm => "LLVM",
Self::Microsoft => "Microsoft",
Self::Mozilla => "Mozilla",
Self::WebKit => "WebKit",
Self::Custom(custom) => custom.as_str(),
}
}
}
#[derive(Error, Debug)]
enum ClangFormatError {
#[error(transparent)]
Io(#[from] std::io::Error),
#[error(transparent)]
FromUtf8Error(#[from] std::string::FromUtf8Error),
#[error("Clang format process exited with a non-zero status")]
NonZeroExitStatus,
}
pub fn clang_format_with_style(
input: &str,
style: &ClangFormatStyle,
) -> Result<String, impl std::error::Error> {
let clang_binary = env::var("CLANG_FORMAT_BINARY").unwrap_or("clang-format".to_string());
let mut child = Command::new(clang_binary.as_str())
.arg(format!("--style={}", style.as_str()))
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()?;
{
let mut stdin = child.stdin.take().expect("no stdin handle");
write!(stdin, "{}", input)?;
}
let output = child.wait_with_output()?;
if output.status.success() {
Ok(String::from_utf8(output.stdout)?)
} else {
Err(ClangFormatError::NonZeroExitStatus)
}
}
pub fn clang_format(input: &str) -> Result<String, impl std::error::Error> {
clang_format_with_style(input, &ClangFormatStyle::Default)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn format_default() {
let input = r#"
struct Test {
};
"#;
let output = clang_format_with_style(input, &ClangFormatStyle::Default);
assert!(output.is_ok());
assert_eq!(output.unwrap(), "\nstruct Test {};\n");
}
#[test]
fn format_mozilla() {
let input = r#"
struct Test {
};
"#;
let output = clang_format_with_style(input, &ClangFormatStyle::Mozilla);
assert!(output.is_ok());
assert_eq!(output.unwrap(), "\nstruct Test\n{};\n");
}
#[test]
fn format_custom() {
let input = r#"
struct Test {
bool field;
};
"#;
{
let output = clang_format_with_style(
input,
&ClangFormatStyle::Custom(
"{BasedOnStyle: 'Mozilla',
IndentWidth: 8}"
.to_string(),
),
);
assert!(output.is_ok());
assert_eq!(
output.unwrap(),
"\nstruct Test\n{\n bool field;\n};\n"
);
}
{
let output = clang_format_with_style(
input,
&ClangFormatStyle::Custom(
"{ BasedOnStyle: \"Mozilla\", IndentWidth: 4 }".to_string(),
),
);
assert!(output.is_ok());
assert_eq!(output.unwrap(), "\nstruct Test\n{\n bool field;\n};\n");
}
}
}