Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
**/*.rs.bk
*.pdb
.envrc
.idea/
.vscode/
debug/
Expand Down
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

43 changes: 29 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,23 +50,36 @@ fn main() {
//.check_ranges(FeatureConfig::Never) // or look below for an example.
.build();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we add imports to have working example in README?

use dbc_codegen::Config;
use std::fs::write;
use std::{env::var, path::PathBuf};

dbc_file should be as a string now:

let dbc_file = std::fs::read_to_string(dbc_path).unwrap();


let mut out = std::io::BufWriter::new(std::fs::File::create("src/messages.rs").unwrap());
let mut out = Vec::new();
dbc_codegen::codegen(config, &mut out).expect("dbc-codegen failed");
write(
PathBuf::from(var("OUT_DIR").unwrap()).join("messages.rs"),
String::from_utf8(out).unwrap(),
).unwrap();
}
```

## Using generated Rust code

dbc-codegen generates a Rust file that is expected to be in a cargo project.
dbc-codegen generates a Rust file that is usually placed into the `OUT_DIR` directory.
Here is an example [`testing/can-messages/Cargo.toml`](testing/can-messages/Cargo.toml) which defines dependencies and features that are used in generated message file.

### Project setup

To use the code, add `mod messages` to your `lib.rs` (or `main.rs`).
To use the code, add this code to your `lib.rs` (or `main.rs`):

```rust,ignore
// Import the generated code.
mod messages {
include!(concat!(env!("OUT_DIR"), "/messages.rs"));
}
```

You will most likely want to interact with the generated `Messages` enum, and call `Messages::from_can_message(id, &payload)`.

Note: The generated code contains a lot of documentation.
Give it a try:

```bash
cargo doc --open
```
Expand All @@ -84,7 +97,7 @@ The generator config has the following flags that control what code gets generat

These implementations can be enabled, disabled, or placed behind feature guards, like so:

```rust
```rust,ignore
Config::builder()
// this will generate Debug implementations
.impl_debug(FeatureConfig::Always)
Expand All @@ -106,7 +119,7 @@ If some field name starts with a non-alphabetic character or is a Rust keyword t

For example:

```
```dbc
VAL_ 512 Five 0 "0Off" 1 "1On" 2 "2Oner" 3 "3Onest";
```

Expand All @@ -128,9 +141,9 @@ pub enum BarFive {
SG_ Type : 30|1@0+ (1,0) [0|1] "boolean" Dolor
```

…conflicts with the Rust keyword `type`. Therefore we prefix it with `x`:
…conflicts with the Rust keyword `type`. Therefore, we prefix it with `x`:

```rust
```rust,ignore
pub fn xtype(&self) -> BarType {
match self.xtype_raw() {
false => BarType::X0off,
Expand All @@ -141,6 +154,9 @@ pub fn xtype(&self) -> BarType {
```

## Development
* This project is easier to develop with [just](https://just.systems/man/en/), a modern alternative to `make`.
* To get a list of available commands, run `just`.
* To run tests, use `just test`.

### lorri for Nix

Expand All @@ -154,14 +170,13 @@ ln -s envrc.lorri .envrc

Licensed under either of

- Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)

at your option.
* Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or <https://www.apache.org/licenses/LICENSE-2.0>)
* MIT license ([LICENSE-MIT](LICENSE-MIT) or <https://opensource.org/licenses/MIT>)
at your option.

### Contribution

Unless you explicitly state otherwise, any contribution intentionally
submitted for inclusion in the work by you, as defined in the Apache-2.0
license, shall be dual licensed as above, without any additional terms or
conditions.
submitted for inclusion in the work by you, as defined in the
Apache-2.0 license, shall be dual-licensed as above, without any
additional terms or conditions.
1 change: 1 addition & 0 deletions deny.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
# [possible values: any SPDX 3.11 short identifier (+ optional exception)].
allow = [
"Apache-2.0",
"BSD-3-Clause",
"MIT",
"Unicode-DFS-2016",
]
68 changes: 46 additions & 22 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,16 @@ mod includes;
mod keywords;
mod pad;

static ALLOW_DEADCODE: &str = "#[allow(dead_code)]";
static ALLOW_LINTS: &str = r"#[allow(
clippy::absurd_extreme_comparisons,
clippy::excessive_precision,
clippy::manual_range_contains,
clippy::unnecessary_cast,
clippy::useless_conversion,
unused_comparisons,
)]";

/// Code generator configuration. See module-level docs for an example.
#[derive(TypedBuilder)]
#[non_exhaustive]
Expand Down Expand Up @@ -122,31 +132,13 @@ pub fn codegen(config: Config<'_>, out: impl Write) -> Result<()> {
let mut w = BufWriter::new(out);

writeln!(&mut w, "// Generated code!")?;
writeln!(&mut w, "//")?;
writeln!(
&mut w,
"#![allow(unused_comparisons, unreachable_patterns, unused_imports)]"
)?;
if config.allow_dead_code {
writeln!(&mut w, "#![allow(dead_code)]")?;
}
writeln!(&mut w, "#![allow(clippy::let_and_return, clippy::eq_op)]")?;
writeln!(
&mut w,
"#![allow(clippy::useless_conversion, clippy::unnecessary_cast)]"
)?;
writeln!(
&mut w,
"#![allow(clippy::excessive_precision, clippy::manual_range_contains, clippy::absurd_extreme_comparisons, clippy::too_many_arguments)]"
)?;
writeln!(&mut w, "#![deny(clippy::arithmetic_side_effects)]")?;
writeln!(&mut w)?;
writeln!(
&mut w,
"//! Message definitions from file `{:?}`",
"// Message definitions from file `{}`",
config.dbc_name
)?;
writeln!(&mut w, "//!")?;
writeln!(&mut w, "//! - Version: `{:?}`", dbc.version)?;
writeln!(&mut w, "// Version: {}", dbc.version.0)?;
writeln!(&mut w)?;
writeln!(&mut w, "use core::ops::BitOr;")?;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#[allow(unused_imports)] might be needed for BitOr or ExtendedId/StandardId or checked if its going to be used to prevent errors:

6 | use core::ops::BitOr;
  |     ^^^^^^^^^^^^^^^^
  |
  = note: `-D unused-imports` implied by `-D warnings`
  = help: to override `-D warnings` add `#[allow(unused_imports)]`

writeln!(&mut w, "use bitvec::prelude::*;")?;
Expand Down Expand Up @@ -190,6 +182,10 @@ fn render_dbc(mut w: impl Write, config: &Config<'_>, dbc: &Dbc) -> Result<()> {

fn render_root_enum(mut w: impl Write, dbc: &Dbc, config: &Config<'_>) -> Result<()> {
writeln!(w, "/// All messages")?;
writeln!(w, "{ALLOW_LINTS}")?;
if config.allow_dead_code {
writeln!(&mut w, "{ALLOW_DEADCODE}")?;
}
writeln!(w, "#[derive(Clone)]")?;
config.impl_debug.fmt_attr(&mut w, "derive(Debug)")?;
config
Expand All @@ -208,6 +204,10 @@ fn render_root_enum(mut w: impl Write, dbc: &Dbc, config: &Config<'_>) -> Result
writeln!(&mut w, "}}")?;
writeln!(&mut w)?;

writeln!(w, "{ALLOW_LINTS}")?;
if config.allow_dead_code {
writeln!(&mut w, "{ALLOW_DEADCODE}")?;
}
writeln!(w, "impl Messages {{")?;
{
let mut w = PadAdapter::wrap(&mut w);
Expand Down Expand Up @@ -275,6 +275,10 @@ fn render_message(mut w: impl Write, config: &Config<'_>, msg: &Message, dbc: &D
writeln!(w, "}}")?;
writeln!(w)?;

writeln!(w, "{ALLOW_LINTS}")?;
if config.allow_dead_code {
writeln!(&mut w, "{ALLOW_DEADCODE}")?;
}
writeln!(w, "impl {} {{", type_name(&msg.name))?;
{
let mut w = PadAdapter::wrap(&mut w);
Expand Down Expand Up @@ -954,6 +958,10 @@ fn write_enum(
let signal_rust_type = signal_to_rust_type(signal);

writeln!(w, "/// Defined values for {}", signal.name)?;
writeln!(w, "{ALLOW_LINTS}")?;
if config.allow_dead_code {
writeln!(&mut w, "{ALLOW_DEADCODE}")?;
}
writeln!(w, "#[derive(Clone, Copy, PartialEq)]")?;
config.impl_debug.fmt_attr(&mut w, "derive(Debug)")?;
config
Expand Down Expand Up @@ -1421,6 +1429,10 @@ fn render_multiplexor_enums(
}

writeln!(w, "/// Defined values for multiplexed signal {}", msg.name)?;
writeln!(w, "{ALLOW_LINTS}")?;
if config.allow_dead_code {
writeln!(&mut w, "{ALLOW_DEADCODE}")?;
}

config.impl_debug.fmt_attr(&mut w, "derive(Debug)")?;
config
Expand Down Expand Up @@ -1452,6 +1464,10 @@ fn render_multiplexor_enums(
for (switch_index, multiplexed_signals) in &multiplexed_signals {
let struct_name = multiplexed_enum_variant_name(msg, multiplexor_signal, *switch_index)?;

writeln!(w, "{ALLOW_LINTS}")?;
if config.allow_dead_code {
writeln!(&mut w, "{ALLOW_DEADCODE}")?;
}
config.impl_debug.fmt_attr(&mut w, "derive(Debug)")?;
config
.impl_defmt
Expand All @@ -1462,6 +1478,10 @@ fn render_multiplexor_enums(
writeln!(w, "pub struct {struct_name} {{ raw: [u8; {}] }}", msg.size)?;
writeln!(w)?;

writeln!(w, "{ALLOW_LINTS}")?;
if config.allow_dead_code {
writeln!(&mut w, "{ALLOW_DEADCODE}")?;
}
writeln!(w, "impl {struct_name} {{")?;

writeln!(
Expand All @@ -1488,6 +1508,10 @@ fn render_arbitrary(mut w: impl Write, config: &Config<'_>, msg: &Message) -> Re
FeatureConfig::Never => return Ok(()),
}

writeln!(w, "{ALLOW_LINTS}")?;
if config.allow_dead_code {
writeln!(&mut w, "{ALLOW_DEADCODE}")?;
}
writeln!(
w,
"impl<'a> Arbitrary<'a> for {typ} {{",
Expand Down Expand Up @@ -1543,7 +1567,7 @@ fn render_error(mut w: impl Write, config: &Config<'_>) -> io::Result<()> {
w.write_all(include_bytes!("./includes/errors.rs"))?;

config.impl_error.fmt_cfg(w, |w| {
writeln!(w, "impl std::error::Error for CanError {{}}")
writeln!(w, "impl core::error::Error for CanError {{}}")
})
}

Expand Down
13 changes: 8 additions & 5 deletions testing/can-embedded/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,18 @@ edition = "2021"
default = ["build-messages"]
build-messages = ["dep:can-messages"]
arb = ["dep:arbitrary"]
std = []

[dependencies]
arbitrary = { version = "1.4", optional = true }
bitvec = { version = "1.0", default-features = false }
embedded-can = "0.4.1"
defmt = "1.0.1"
embedded-can = "0.4.1"

# This is optional and default so we can turn it off for the embedded target.
# can-messages is optional and default so we can turn it off for the embedded target.
# Then it doesn't pull in std.
[dependencies.can-messages]
path = "../can-messages"
optional = true
can-messages = { path = "../can-messages", optional = true }

[build-dependencies]
anyhow = "1.0"
dbc-codegen = { path = "../.." }
34 changes: 34 additions & 0 deletions testing/can-embedded/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
use std::env::var;
use std::fs::{read_to_string, write};
use std::path::PathBuf;

use anyhow::Result;
use dbc_codegen::{Config, FeatureConfig};

fn main() -> Result<()> {
let dbc_file = read_to_string("../dbc-examples/example.dbc")?;
println!("cargo:rerun-if-changed=../dbc-examples/example.dbc");
println!("cargo:rerun-if-changed=../../src");
println!("cargo:rerun-if-changed=../can-embedded/src");

let config = Config::builder()
.dbc_name("example.dbc")
.dbc_content(&dbc_file)
.debug_prints(true)
.allow_dead_code(true)
.impl_debug(FeatureConfig::Always)
.impl_defmt(FeatureConfig::Always)
.impl_error(FeatureConfig::Gated("std"))
.impl_arbitrary(FeatureConfig::Gated("arb"))
.check_ranges(FeatureConfig::Always)
.build();

let mut out = Vec::new();
dbc_codegen::codegen(config, &mut out)?;
write(
PathBuf::from(var("OUT_DIR")?).join("messages.rs"),
String::from_utf8(out)?,
)?;

Ok(())
}
8 changes: 4 additions & 4 deletions testing/can-embedded/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#![no_std]

#[rustfmt::skip]
#[allow(clippy::pedantic)]
#[allow(clippy::disallowed_names)]
pub mod messages;
#[expect(clippy::disallowed_names)]
mod messages {
include!(concat!(env!("OUT_DIR"), "/messages.rs"));
}
Copy link

Copilot AI Nov 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The messages module is private but its contents are never re-exported. This means all the generated code (messages, error types, etc.) will be inaccessible to users of this library.

Add pub use messages::*; after line 8 to re-export the generated items, similar to how it's done in testing/can-messages/src/lib.rs.

Suggested change
}
}
pub use messages::*;

Copilot uses AI. Check for mistakes.
1 change: 0 additions & 1 deletion testing/can-embedded/src/messages.rs

This file was deleted.

16 changes: 8 additions & 8 deletions testing/can-messages/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,17 @@ version = "0.1.0"
authors = ["Pascal Hertleif <pascal@technocreatives.com>"]
edition = "2021"

[features]
default = ["arb", "std"]
arb = ["arbitrary"]
std = []

[dependencies]
bitvec = { version = "1.0", default-features = false }
arbitrary = { version = "1.4", optional = true }
embedded-can = "0.4.1"
bitvec = { version = "1.0", default-features = false }
defmt = "1.0.1"
embedded-can = "0.4.1"

[build-dependencies]
anyhow = "1.0"
dbc-codegen = { path = "../../" }

[features]
default = ["arb", "std"]
arb = ["arbitrary"]
std = []
dbc-codegen = { path = "../.." }
Loading