Repo of no-std crates for my personal embedded projects
1use heck::ToUpperCamelCase;
2use std::{io::BufWriter, path::PathBuf};
3
4static MESSAGE_TEMPLATE: &str = include_str!("./templates/message.handlebars");
5
6#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, serde::Serialize)]
7struct ParsedMessageKind {
8 name: String,
9 id: u64,
10}
11
12fn main() -> anyhow::Result<()> {
13 let proto_files = ["protos/api.proto", "protos/api_options.proto"];
14 let includes = ["protos"];
15
16 prost_build::Config::new()
17 .btree_map(["."])
18 .message_attribute(
19 ".",
20 "#[cfg_attr(feature = \"defmt\", derive(defmt::Format))]",
21 )
22 .default_package_filename("api")
23 .compile_protos(&proto_files, &includes)?;
24
25 let valid_sources = 0..=2;
26
27 let mut message_kinds: Vec<ParsedMessageKind> = protobuf_parse::Parser::new()
28 .inputs(proto_files)
29 .includes(includes)
30 .parse_and_typecheck()?
31 .file_descriptors
32 .into_iter()
33 .flat_map(|fd| fd.message_type)
34 .filter_map(|message| {
35 if let Some(protobuf::UnknownValueRef::Varint(id)) = message
36 .options
37 .get_or_default()
38 .special_fields
39 .unknown_fields()
40 .get(1036)
41 && let Some(protobuf::UnknownValueRef::Varint(source)) = message
42 .options
43 .get_or_default()
44 .special_fields
45 .unknown_fields()
46 .get(1037)
47 && valid_sources.contains(&source)
48 {
49 Some(ParsedMessageKind {
50 name: sanitize_identifier(message.name().to_upper_camel_case()),
51 id,
52 })
53 } else {
54 None
55 }
56 })
57 .collect();
58
59 message_kinds.sort();
60
61 let out_dir = PathBuf::from(std::env::var("OUT_DIR")?).join("api.rs");
62
63 let api_constants = BufWriter::new(std::fs::OpenOptions::new().append(true).open(out_dir)?);
64
65 handlebars::Handlebars::new().render_template_to_write(
66 MESSAGE_TEMPLATE,
67 &message_kinds,
68 api_constants,
69 )?;
70
71 Ok(())
72}
73
74/// From prost-build, as it is in a private module.
75fn sanitize_identifier(ident: String) -> String {
76 // Use a raw identifier if the identifier matches a Rust keyword:
77 // https://doc.rust-lang.org/reference/keywords.html.
78 match ident.as_str() {
79 // 2015 strict keywords.
80 | "as" | "break" | "const" | "continue" | "else" | "enum" | "false"
81 | "fn" | "for" | "if" | "impl" | "in" | "let" | "loop" | "match" | "mod" | "move" | "mut"
82 | "pub" | "ref" | "return" | "static" | "struct" | "trait" | "true"
83 | "type" | "unsafe" | "use" | "where" | "while"
84 // 2018 strict keywords.
85 | "dyn"
86 // 2015 reserved keywords.
87 | "abstract" | "become" | "box" | "do" | "final" | "macro" | "override" | "priv" | "typeof"
88 | "unsized" | "virtual" | "yield"
89 // 2018 reserved keywords.
90 | "async" | "await" | "try"
91 // 2024 reserved keywords.
92 | "gen" => format!("r#{ident}"),
93 // the following keywords are not supported as raw identifiers and are therefore suffixed with an underscore.
94 "_" | "super" | "self" | "Self" | "extern" | "crate" => format!("{ident}_"),
95 // the following keywords begin with a number and are therefore prefixed with an underscore.
96 s if s.starts_with(char::is_numeric) => format!("_{ident}"),
97 _ => ident,
98 }
99}