Skip to content

Commit

Permalink
rust: implement clang output command field support for writing
Browse files Browse the repository at this point in the history
  • Loading branch information
rizsotto committed Nov 19, 2024
1 parent 9f317ff commit c7e965e
Show file tree
Hide file tree
Showing 5 changed files with 120 additions and 10 deletions.
4 changes: 2 additions & 2 deletions rust/bear/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -519,9 +519,9 @@ pub enum OutputFields {
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub struct Format {
#[serde(default = "default_enabled")]
command_as_array: bool,
pub command_as_array: bool,
#[serde(default = "default_disabled")]
drop_output_field: bool,
pub drop_output_field: bool,
}

impl Default for Format {
Expand Down
24 changes: 22 additions & 2 deletions rust/bear/src/output/clang/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,18 +39,38 @@ pub struct Entry {
pub output: Option<std::path::PathBuf>,
}

pub fn write(
/// Serialize entries from an iterator into a JSON array.
///
/// It uses the `arguments` field of the `Entry` struct to serialize the array of strings.
pub fn write_with_arguments(
writer: impl std::io::Write,
entries: impl Iterator<Item = Entry>,
) -> Result<(), Error> {
let mut ser = serde_json::Serializer::new(writer);
let mut ser = serde_json::Serializer::pretty(writer);
let mut seq = ser.serialize_seq(None)?;
for entry in entries {
seq.serialize_element(&entry)?;
}
seq.end()
}

/// Serialize entries from an iterator into a JSON array.
///
/// It uses the `arguments` field of the `Entry` struct to serialize the array of strings.
pub fn write_with_command(
writer: impl std::io::Write,
entries: impl Iterator<Item = Entry>,
) -> Result<(), Error> {
let mut ser = serde_json::Serializer::pretty(writer);
let mut seq = ser.serialize_seq(None)?;
for entry in entries {
let entry = type_ser::EntryWithCommand::from(entry);
seq.serialize_element(&entry)?;
}
seq.end()
}

/// Deserialize entries from a JSON array into an iterator.
pub fn read(reader: impl std::io::Read) -> impl Iterator<Item = Result<Entry, Error>> {
iterator::iter_json_array(reader)
}
58 changes: 54 additions & 4 deletions rust/bear/src/output/clang/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,6 @@ mod success {
mod basic {
use super::*;
use crate::vec_of_strings;
use serde_json::Value;
use std::io::{Cursor, Seek, SeekFrom};

fn expected_values() -> Vec<Entry> {
Expand Down Expand Up @@ -166,17 +165,35 @@ mod success {

// Create fake "file"
let mut buffer = Cursor::new(Vec::new());
let result = write(&mut buffer, input.into_iter());
let result = write_with_arguments(&mut buffer, input.into_iter());
assert!(result.is_ok());

// Use the fake "file" as input
buffer.seek(SeekFrom::Start(0)).unwrap();
let content: Value = serde_json::from_reader(&mut buffer)?;
let content: serde_json::Value = serde_json::from_reader(&mut buffer)?;

assert_eq!(expected_with_array_syntax(), content);

Ok(())
}

#[test]
fn save_with_string_command_syntax() -> Result<(), Error> {
let input = expected_values();

// Create fake "file"
let mut buffer = Cursor::new(Vec::new());
let result = write_with_command(&mut buffer, input.into_iter());
assert!(result.is_ok());

// Use the fake "file" as input
buffer.seek(SeekFrom::Start(0)).unwrap();
let content: serde_json::Value = serde_json::from_reader(&mut buffer)?;

assert_eq!(expected_with_string_syntax(), content);

Ok(())
}
}

mod quoted {
Expand Down Expand Up @@ -233,6 +250,21 @@ mod success {
])
}

fn expected_with_string_syntax() -> serde_json::Value {
json!([
{
"directory": "/home/user",
"file": "./file_a.c",
"command": r#"cc -c -D 'name=\"me\"' ./file_a.c -o ./file_a.o"#
},
{
"directory": "/home/user",
"file": "./file_b.c",
"command": r#"cc -c -D 'name="me"' ./file_b.c -o ./file_b.o"#
}
])
}

#[test]
fn load_content_with_array_command_syntax() {
let content = expected_with_array_syntax().to_string();
Expand All @@ -249,7 +281,7 @@ mod success {

// Create fake "file"
let mut buffer = Cursor::new(Vec::new());
let result = write(&mut buffer, input.into_iter());
let result = write_with_arguments(&mut buffer, input.into_iter());
assert!(result.is_ok());

// Use the fake "file" as input
Expand All @@ -260,5 +292,23 @@ mod success {

Ok(())
}

#[test]
fn save_with_string_command_syntax() -> Result<(), Error> {
let input = expected_values();

// Create fake "file"
let mut buffer = Cursor::new(Vec::new());
let result = write_with_command(&mut buffer, input.into_iter());
assert!(result.is_ok());

// Use the fake "file" as input
buffer.seek(SeekFrom::Start(0)).unwrap();
let content: Value = serde_json::from_reader(&mut buffer)?;

assert_eq!(expected_with_string_syntax(), content);

Ok(())
}
}
}
36 changes: 36 additions & 0 deletions rust/bear/src/output/clang/type_ser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,39 @@ impl Serialize for Entry {
state.end()
}
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct EntryWithCommand {
pub file: std::path::PathBuf,
pub command: String,
pub directory: std::path::PathBuf,
pub output: Option<std::path::PathBuf>,
}

impl From<Entry> for EntryWithCommand {
fn from(entry: Entry) -> Self {
Self {
file: entry.file,
command: shell_words::join(&entry.arguments),
directory: entry.directory,
output: entry.output,
}
}
}

impl Serialize for EntryWithCommand {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let size = if self.output.is_some() { 4 } else { 3 };
let mut state = serializer.serialize_struct("Entry", size)?;
state.serialize_field("directory", &self.directory)?;
state.serialize_field("file", &self.file)?;
state.serialize_field("command", &self.command)?;
if self.output.is_some() {
state.serialize_field("output", &self.output)?;
}
state.end()
}
}
8 changes: 6 additions & 2 deletions rust/bear/src/output/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,15 +94,19 @@ impl OutputWriter {
&self,
entries: impl Iterator<Item = Entry>,
) -> anyhow::Result<PathBuf> {
// FIXME: Implement entry formatting.
// FIXME: Implement entry formatting. (dropping the output field before this function)

// Generate a temporary file name.
let file_name = self.output.with_extension("tmp");
// Open the file for writing.
let file = File::create(&file_name)
.with_context(|| format!("Failed to create file: {:?}", file_name.as_path()))?;
// Write the entries to the file.
clang::write(BufWriter::new(file), entries)?;
if self.format.command_as_array {
clang::write_with_arguments(BufWriter::new(file), entries)?
} else {
clang::write_with_command(BufWriter::new(file), entries)?
}
// Return the temporary file name.
Ok(file_name)
}
Expand Down

0 comments on commit c7e965e

Please sign in to comment.