use super::Dbc;
use crate::Result;
use std::fmt::{Display, Formatter, Result as FmtResult};
use std::path::Path;
impl Dbc {
pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
let content = std::fs::read_to_string(path.as_ref())?;
Self::parse(&content)
}
#[must_use = "return value should be used"]
pub fn to_dbc_string(&self) -> String {
let signal_count: usize = self.messages().iter().map(|m| m.signals().len()).sum();
let estimated_capacity = 200 + (self.messages.len() * 50) + (signal_count * 100);
let mut result = String::with_capacity(estimated_capacity);
if let Some(version) = &self.version {
result.push_str(&version.to_dbc_string());
result.push_str("\n\n");
}
if let Some(ref bit_timing) = self.bit_timing {
result.push_str(&bit_timing.to_string());
result.push_str("\n\n");
} else {
result.push_str("BS_:\n\n");
}
result.push_str(&self.nodes.to_dbc_string());
result.push('\n');
for message in self.messages().iter() {
result.push('\n');
result.push_str(&message.to_string_full());
}
if let Some(comment) = self.comment() {
result.push_str("\nCM_ \"");
result.push_str(comment);
result.push_str("\";\n");
}
for node in self.nodes.iter_nodes() {
if let Some(comment) = node.comment() {
result.push_str("CM_ BU_ ");
result.push_str(node.name());
result.push_str(" \"");
result.push_str(comment);
result.push_str("\";\n");
}
}
for message in self.messages().iter() {
if let Some(comment) = message.comment() {
result.push_str("CM_ BO_ ");
result.push_str(&message.id().to_string());
result.push_str(" \"");
result.push_str(comment);
result.push_str("\";\n");
}
for signal in message.signals().iter() {
if let Some(comment) = signal.comment() {
result.push_str("CM_ SG_ ");
result.push_str(&message.id().to_string());
result.push(' ');
result.push_str(signal.name());
result.push_str(" \"");
result.push_str(comment);
result.push_str("\";\n");
}
}
}
result
}
}
impl Display for Dbc {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
write!(f, "{}", self.to_dbc_string())
}
}
#[cfg(test)]
mod tests {
use crate::Dbc;
#[test]
fn test_to_dbc_string() {
let dbc = Dbc::parse(
r#"VERSION "1.0"
BU_: ECM
BO_ 256 Engine : 8 ECM
SG_ RPM : 0|16@1+ (0.25,0) [0|8000] "rpm"
"#,
)
.unwrap();
let dbc_string = dbc.to_dbc_string();
assert!(dbc_string.contains("VERSION"));
assert!(dbc_string.contains("BU_"));
assert!(dbc_string.contains("BO_"));
assert!(dbc_string.contains("SG_"));
}
#[test]
fn test_display() {
let dbc = Dbc::parse(
r#"VERSION "1.0"
BU_: ECM
BO_ 256 Engine : 8 ECM
"#,
)
.unwrap();
let display_str = format!("{}", dbc);
assert!(display_str.contains("VERSION"));
}
#[test]
fn test_save_round_trip() {
let original = r#"VERSION "1.0"
BU_: ECM TCM
BO_ 256 EngineData : 8 ECM
SG_ RPM : 0|16@1+ (0.25,0) [0|8000] "rpm" *
SG_ Temperature : 16|8@0- (1,-40) [-40|215] "°C" TCM
BO_ 512 BrakeData : 4 TCM
SG_ Pressure : 0|16@0+ (0.1,0) [0|1000] "bar"
"#;
let dbc = Dbc::parse(original).unwrap();
let saved = dbc.to_dbc_string();
let dbc2 = Dbc::parse(&saved).unwrap();
assert_eq!(
dbc.version().map(|v| v.to_string()),
dbc2.version().map(|v| v.to_string())
);
assert_eq!(dbc.messages().len(), dbc2.messages().len());
for (msg1, msg2) in dbc.messages().iter().zip(dbc2.messages().iter()) {
assert_eq!(msg1.id(), msg2.id());
assert_eq!(msg1.name(), msg2.name());
assert_eq!(msg1.dlc(), msg2.dlc());
assert_eq!(msg1.sender(), msg2.sender());
assert_eq!(msg1.signals().len(), msg2.signals().len());
for (sig1, sig2) in msg1.signals().iter().zip(msg2.signals().iter()) {
assert_eq!(sig1.name(), sig2.name());
assert_eq!(sig1.start_bit(), sig2.start_bit());
assert_eq!(sig1.length(), sig2.length());
assert_eq!(sig1.byte_order(), sig2.byte_order());
assert_eq!(sig1.is_unsigned(), sig2.is_unsigned());
assert_eq!(sig1.factor(), sig2.factor());
assert_eq!(sig1.offset(), sig2.offset());
assert_eq!(sig1.min(), sig2.min());
assert_eq!(sig1.max(), sig2.max());
assert_eq!(sig1.unit(), sig2.unit());
assert_eq!(sig1.receivers(), sig2.receivers());
}
}
}
#[test]
fn test_save_basic() {
let dbc_content = r#"VERSION "1.0"
BU_: ECM
BO_ 256 EngineData : 8 ECM
SG_ RPM : 0|16@0+ (0.25,0) [0|8000] "rpm" *
"#;
let dbc = Dbc::parse(dbc_content).unwrap();
let saved = dbc.to_dbc_string();
assert!(saved.contains("VERSION \"1.0\""));
assert!(saved.contains("BU_: ECM"));
assert!(saved.contains("BO_ 256 EngineData : 8 ECM"));
assert!(saved.contains("SG_ RPM : 0|16@0+ (0.25,0) [0|8000] \"rpm\" Vector__XXX"));
}
#[test]
fn test_save_multiple_messages() {
let dbc_content = r#"VERSION "1.0"
BU_: ECM TCM
BO_ 256 EngineData : 8 ECM
SG_ RPM : 0|16@0+ (0.25,0) [0|8000] "rpm"
BO_ 512 BrakeData : 4 TCM
SG_ Pressure : 0|16@1+ (0.1,0) [0|1000] "bar"
"#;
let dbc = Dbc::parse(dbc_content).unwrap();
let saved = dbc.to_dbc_string();
assert!(saved.contains("BO_ 256 EngineData : 8 ECM"));
assert!(saved.contains("BO_ 512 BrakeData : 4 TCM"));
assert!(saved.contains("SG_ RPM"));
assert!(saved.contains("SG_ Pressure"));
}
#[test]
fn test_bit_timing_empty() {
let dbc = Dbc::parse(
r#"VERSION "1.0"
BS_:
BU_: ECM
BO_ 256 Engine : 8 ECM
"#,
)
.unwrap();
assert!(dbc.bit_timing().is_none());
let saved = dbc.to_dbc_string();
assert!(saved.contains("BS_:"));
}
#[test]
fn test_bit_timing_with_values() {
let dbc = Dbc::parse(
r#"VERSION "1.0"
BS_: 500000 : 1,2
BU_: ECM
BO_ 256 Engine : 8 ECM
"#,
)
.unwrap();
let bt = dbc.bit_timing().expect("bit timing should be present");
assert_eq!(bt.baudrate(), Some(500000));
assert_eq!(bt.btr1(), Some(1));
assert_eq!(bt.btr2(), Some(2));
let saved = dbc.to_dbc_string();
assert!(saved.contains("BS_: 500000 : 1,2"));
}
#[test]
fn test_bit_timing_baudrate_only() {
let dbc = Dbc::parse(
r#"VERSION "1.0"
BS_: 500000
BU_: ECM
BO_ 256 Engine : 8 ECM
"#,
)
.unwrap();
let bt = dbc.bit_timing().expect("bit timing should be present");
assert_eq!(bt.baudrate(), Some(500000));
assert_eq!(bt.btr1(), None);
assert_eq!(bt.btr2(), None);
let saved = dbc.to_dbc_string();
assert!(saved.contains("BS_: 500000"));
}
}