Skip to content

Commit 48dee7f

Browse files
committed
rust: clang compilation database simplifications
1 parent e30868e commit 48dee7f

File tree

4 files changed

+106
-170
lines changed

4 files changed

+106
-170
lines changed

rust/bear/src/output/clang/iterator.rs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,8 @@ where
3131
R: io::Read,
3232
{
3333
match state {
34-
State::AtStart => {
35-
if read_skipping_ws(&mut reader)? == b'[' {
36-
// read the next char to see if the array is empty
34+
State::AtStart => match read_skipping_ws(&mut reader)? {
35+
b'[' => {
3736
let peek = read_skipping_ws(&mut reader)?;
3837
if peek == b']' {
3938
*state = State::Finished;
@@ -42,11 +41,12 @@ where
4241
*state = State::AtMiddle;
4342
deserialize_single(io::Cursor::new([peek]).chain(reader)).map(Some)
4443
}
45-
} else {
44+
}
45+
_ => {
4646
*state = State::Failed;
4747
Err(serde::de::Error::custom("expected `[`"))
4848
}
49-
}
49+
},
5050
State::AtMiddle => match read_skipping_ws(&mut reader)? {
5151
b',' => deserialize_single(reader).map(Some),
5252
b']' => {
@@ -58,8 +58,7 @@ where
5858
Err(serde::de::Error::custom("expected `,` or `]`"))
5959
}
6060
},
61-
State::Finished => Ok(None),
62-
State::Failed => Ok(None),
61+
State::Finished | State::Failed => Ok(None),
6362
}
6463
}
6564

@@ -78,9 +77,10 @@ where
7877
fn read_skipping_ws(mut reader: impl io::Read) -> Result<u8> {
7978
loop {
8079
let mut byte = 0u8;
81-
if let Err(io) = reader.read_exact(std::slice::from_mut(&mut byte)) {
82-
return Err(Error::io(io));
83-
}
80+
reader
81+
.read_exact(std::slice::from_mut(&mut byte))
82+
.map_err(Error::io)?;
83+
8484
if !byte.is_ascii_whitespace() {
8585
return Ok(byte);
8686
}

rust/bear/src/output/clang/mod.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
//! LLVM project [documentation](https://clang.llvm.org/docs/JSONCompilationDatabase.html).
1313
1414
use serde::ser::{SerializeSeq, Serializer};
15+
use serde::Serialize;
1516
use serde_json::Error;
1617

1718
mod iterator;
@@ -20,7 +21,7 @@ mod type_de;
2021
mod type_ser;
2122

2223
/// Represents an entry of the compilation database.
23-
#[derive(Clone, Debug, PartialEq, Eq)]
24+
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
2425
pub struct Entry {
2526
/// The main translation unit source processed by this compilation step.
2627
/// This is used by tools as the key into the compilation database.
@@ -36,6 +37,7 @@ pub struct Entry {
3637
pub directory: std::path::PathBuf,
3738
/// The name of the output created by this compilation step. This field is optional.
3839
/// It can be used to distinguish different processing modes of the same input file.
40+
#[serde(skip_serializing_if = "Option::is_none")]
3941
pub output: Option<std::path::PathBuf>,
4042
}
4143

rust/bear/src/output/clang/type_de.rs

Lines changed: 90 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -2,145 +2,113 @@
22

33
//! Implements deserialization of the `Entry` struct.
44
5+
use serde::de::{self, Deserialize, Deserializer, MapAccess, Visitor};
56
use std::fmt;
67
use std::path;
78

8-
use serde::de::{self, Deserialize, Deserializer, MapAccess, Visitor};
9-
109
use super::Entry;
1110

1211
impl<'de> Deserialize<'de> for Entry {
1312
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1413
where
1514
D: Deserializer<'de>,
1615
{
17-
enum Field {
18-
Directory,
19-
File,
20-
Command,
21-
Arguments,
22-
Output,
23-
}
24-
const FIELDS: &[&str] = &["directory", "file", "command", "arguments", "output"];
25-
26-
impl<'de> Deserialize<'de> for Field {
27-
fn deserialize<D>(deserializer: D) -> Result<Field, D::Error>
28-
where
29-
D: Deserializer<'de>,
30-
{
31-
struct FieldVisitor;
32-
33-
impl Visitor<'_> for FieldVisitor {
34-
type Value = Field;
35-
36-
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
37-
formatter
38-
.write_str("`directory`, `file`, `command`, `arguments`, or `output`")
39-
}
40-
41-
fn visit_str<E>(self, value: &str) -> Result<Field, E>
42-
where
43-
E: de::Error,
44-
{
45-
match value {
46-
"directory" => Ok(Field::Directory),
47-
"file" => Ok(Field::File),
48-
"command" => Ok(Field::Command),
49-
"arguments" => Ok(Field::Arguments),
50-
"output" => Ok(Field::Output),
51-
_ => Err(de::Error::unknown_field(value, FIELDS)),
52-
}
53-
}
54-
}
55-
56-
deserializer.deserialize_identifier(FieldVisitor)
57-
}
16+
deserializer.deserialize_struct("Entry", FIELDS, EntryVisitor)
17+
}
18+
}
19+
20+
enum Field {
21+
Directory,
22+
File,
23+
Command,
24+
Arguments,
25+
Output,
26+
}
27+
28+
const FIELDS: &[&str] = &["directory", "file", "command", "arguments", "output"];
29+
30+
impl<'de> Deserialize<'de> for Field {
31+
fn deserialize<D>(deserializer: D) -> Result<Field, D::Error>
32+
where
33+
D: Deserializer<'de>,
34+
{
35+
deserializer.deserialize_identifier(FieldVisitor)
36+
}
37+
}
38+
39+
struct FieldVisitor;
40+
41+
impl Visitor<'_> for FieldVisitor {
42+
type Value = Field;
43+
44+
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
45+
write!(formatter, "one of {:?}", FIELDS)
46+
}
47+
48+
fn visit_str<E>(self, value: &str) -> Result<Field, E>
49+
where
50+
E: de::Error,
51+
{
52+
match value {
53+
"directory" => Ok(Field::Directory),
54+
"file" => Ok(Field::File),
55+
"command" => Ok(Field::Command),
56+
"arguments" => Ok(Field::Arguments),
57+
"output" => Ok(Field::Output),
58+
_ => Err(de::Error::unknown_field(value, FIELDS)),
5859
}
60+
}
61+
}
5962

60-
struct EntryVisitor;
63+
struct EntryVisitor;
6164

62-
impl<'de> Visitor<'de> for EntryVisitor {
63-
type Value = Entry;
65+
impl<'de> Visitor<'de> for EntryVisitor {
66+
type Value = Entry;
6467

65-
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
66-
formatter.write_str("struct Entry")
67-
}
68+
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
69+
formatter.write_str("object Entry")
70+
}
6871

69-
fn visit_map<V>(self, mut map: V) -> Result<Entry, V::Error>
70-
where
71-
V: MapAccess<'de>,
72-
{
73-
let mut directory: Option<path::PathBuf> = None;
74-
let mut file: Option<path::PathBuf> = None;
75-
let mut command: Option<String> = None;
76-
let mut arguments: Option<Vec<String>> = None;
77-
let mut output: Option<path::PathBuf> = None;
78-
79-
while let Some(key) = map.next_key()? {
80-
match key {
81-
Field::Directory => {
82-
if directory.is_some() {
83-
return Err(de::Error::duplicate_field("directory"));
84-
}
85-
directory = Some(map.next_value()?);
86-
}
87-
Field::File => {
88-
if file.is_some() {
89-
return Err(de::Error::duplicate_field("file"));
90-
}
91-
file = Some(map.next_value()?);
92-
}
93-
Field::Command => {
94-
if command.is_some() {
95-
return Err(de::Error::duplicate_field("command"));
96-
}
97-
command = Some(map.next_value()?);
98-
}
99-
Field::Arguments => {
100-
if arguments.is_some() {
101-
return Err(de::Error::duplicate_field("arguments"));
102-
}
103-
arguments = Some(map.next_value()?);
104-
}
105-
Field::Output => {
106-
if output.is_some() {
107-
return Err(de::Error::duplicate_field("output"));
108-
}
109-
output = Some(map.next_value()?);
110-
}
111-
}
112-
}
113-
let directory = directory.ok_or_else(|| de::Error::missing_field("directory"))?;
114-
let file = file.ok_or_else(|| de::Error::missing_field("file"))?;
115-
if arguments.is_some() && command.is_some() {
116-
return Err(de::Error::custom(
117-
"Either `command` or `arguments` field need to be specified, but not both.",
118-
));
119-
}
120-
let arguments = arguments.map_or_else(
121-
|| {
122-
command
123-
.ok_or_else(|| de::Error::missing_field("`command` or `arguments`"))
124-
.and_then(|cmd| {
125-
shell_words::split(cmd.as_str()).map_err(|_| {
126-
de::Error::invalid_value(
127-
de::Unexpected::Str(cmd.as_str()),
128-
&"quotes needs to be matched",
129-
)
130-
})
131-
})
132-
},
133-
Ok,
134-
)?;
135-
Ok(Entry {
136-
directory,
137-
file,
138-
arguments,
139-
output,
140-
})
72+
fn visit_map<V>(self, mut map: V) -> Result<Entry, V::Error>
73+
where
74+
V: MapAccess<'de>,
75+
{
76+
let mut directory_opt: Option<path::PathBuf> = None;
77+
let mut file_opt: Option<path::PathBuf> = None;
78+
let mut command_opt: Option<String> = None;
79+
let mut arguments_opt: Option<Vec<String>> = None;
80+
let mut output: Option<path::PathBuf> = None;
81+
82+
while let Some(key) = map.next_key()? {
83+
match key {
84+
Field::Directory => directory_opt = Some(map.next_value()?),
85+
Field::File => file_opt = Some(map.next_value()?),
86+
Field::Command => command_opt = Some(map.next_value()?),
87+
Field::Arguments => arguments_opt = Some(map.next_value()?),
88+
Field::Output => output = Some(map.next_value()?),
14189
}
14290
}
14391

144-
deserializer.deserialize_struct("Entry", FIELDS, EntryVisitor)
92+
// Validate if the mandatory fields are present.
93+
let arguments = match (arguments_opt, command_opt) {
94+
(None, None) => Err(de::Error::missing_field("`command` or `arguments`")),
95+
(Some(_), Some(_)) => Err(de::Error::custom(
96+
"Either `command` or `arguments` field need to be specified, but not both.",
97+
)),
98+
(Some(args), None) => Ok(args),
99+
(None, Some(cmd)) => shell_words::split(cmd.as_str()).map_err(|_| {
100+
de::Error::invalid_value(
101+
de::Unexpected::Str(cmd.as_str()),
102+
&"valid shell command with proper escaping",
103+
)
104+
}),
105+
}?;
106+
107+
Ok(Entry {
108+
directory: directory_opt.ok_or_else(|| de::Error::missing_field("directory"))?,
109+
file: file_opt.ok_or_else(|| de::Error::missing_field("file"))?,
110+
arguments,
111+
output,
112+
})
145113
}
146114
}

rust/bear/src/output/clang/type_ser.rs

Lines changed: 3 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -2,32 +2,15 @@
22

33
//! Implements serialization of the `Entry` struct.
44
5-
use serde::ser::{Serialize, SerializeStruct, Serializer};
6-
75
use super::Entry;
6+
use serde::Serialize;
87

9-
impl Serialize for Entry {
10-
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
11-
where
12-
S: Serializer,
13-
{
14-
let size = if self.output.is_some() { 4 } else { 3 };
15-
let mut state = serializer.serialize_struct("Entry", size)?;
16-
state.serialize_field("directory", &self.directory)?;
17-
state.serialize_field("file", &self.file)?;
18-
state.serialize_field("arguments", &self.arguments)?;
19-
if self.output.is_some() {
20-
state.serialize_field("output", &self.output)?;
21-
}
22-
state.end()
23-
}
24-
}
25-
26-
#[derive(Clone, Debug, PartialEq, Eq)]
8+
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
279
pub struct EntryWithCommand {
2810
pub file: std::path::PathBuf,
2911
pub command: String,
3012
pub directory: std::path::PathBuf,
13+
#[serde(skip_serializing_if = "Option::is_none")]
3114
pub output: Option<std::path::PathBuf>,
3215
}
3316

@@ -41,20 +24,3 @@ impl From<Entry> for EntryWithCommand {
4124
}
4225
}
4326
}
44-
45-
impl Serialize for EntryWithCommand {
46-
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
47-
where
48-
S: Serializer,
49-
{
50-
let size = if self.output.is_some() { 4 } else { 3 };
51-
let mut state = serializer.serialize_struct("Entry", size)?;
52-
state.serialize_field("directory", &self.directory)?;
53-
state.serialize_field("file", &self.file)?;
54-
state.serialize_field("command", &self.command)?;
55-
if self.output.is_some() {
56-
state.serialize_field("output", &self.output)?;
57-
}
58-
state.end()
59-
}
60-
}

0 commit comments

Comments
 (0)