Skip to content

Commit 69dba37

Browse files
committed
htmlcompare
1 parent 64074d9 commit 69dba37

File tree

6 files changed

+269
-3
lines changed

6 files changed

+269
-3
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ clap = { version = "4.1", features = ["derive", "cargo", "color"] }
2828
serde = { version = "1.0", features = ["derive"] }
2929
quick-xml = { version = "0.28", features = ["serialize"] }
3030
svd-rs = { version = "0.14.2", features = ["serde", "derive-from"] }
31-
svd-parser = { version = "0.14.1", features = ["expand"] }
31+
svd-parser = { version = "0.14.2", features = ["expand"] }
3232
svd-encoder = "0.14.3"
3333
yaml-rust = "0.4"
3434
# serde_yaml 0.9.x looks broken

src/cli.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ use clap::Parser;
33
use std::path::PathBuf;
44

55
use svdtools::{
6-
convert::convert_cli, interrupts::interrupts_cli, makedeps::makedeps_cli, mmap::mmap_cli,
7-
patch::patch_cli,
6+
convert::convert_cli, html::htmlcompare_cli, interrupts::interrupts_cli,
7+
makedeps::makedeps_cli, mmap::mmap_cli, patch::patch_cli,
88
};
99

1010
#[derive(Parser, Debug)]
@@ -82,6 +82,13 @@ enum Command {
8282
#[clap(long)]
8383
format_config: Option<PathBuf>,
8484
},
85+
Htmlcompare {
86+
/// Path to output html files
87+
htmldir: PathBuf,
88+
89+
/// Input SVD XML files
90+
svdfiles: Vec<PathBuf>,
91+
},
8592
}
8693

8794
impl Command {
@@ -123,6 +130,9 @@ impl Command {
123130
},
124131
format_config.as_ref().map(|p| p.as_path()),
125132
)?,
133+
Self::Htmlcompare { htmldir, svdfiles } => {
134+
htmlcompare_cli::htmlcompare(htmldir, svdfiles)?;
135+
}
126136
}
127137
Ok(())
128138
}

src/html/html_cli.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+

src/html/htmlcompare_cli.rs

Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
use anyhow::Result;
2+
use std::{
3+
collections::{BTreeMap, HashMap},
4+
fs::File,
5+
io::{Read, Write},
6+
path::{Path, PathBuf},
7+
};
8+
use svd_parser::Config;
9+
use svd_rs::Device;
10+
11+
struct Part {
12+
pub name: String,
13+
pub device: Device,
14+
}
15+
16+
fn parse(in_path: &Path) -> Result<Part> {
17+
let mut input = String::new();
18+
File::open(in_path)?.read_to_string(&mut input)?;
19+
20+
let device = svd_parser::parse_with_config(&input, &Config::default().expand(true))?;
21+
let name = in_path
22+
.file_stem()
23+
.unwrap()
24+
.to_str()
25+
.unwrap()
26+
.split('.')
27+
.next()
28+
.unwrap()
29+
.to_string();
30+
//dbg!(&name);
31+
Ok(Part { name, device })
32+
}
33+
34+
fn html_page(title: &str, table: &str) -> String {
35+
let title = format!("<title>{title}</title>");
36+
let header = format!("<h1>{title}</h1>");
37+
let out = [
38+
r##"
39+
<!DOCTYPE html>
40+
<html>
41+
<head>
42+
<meta charset="UTF-8">"##,
43+
&title,
44+
r##"</head>
45+
<style>
46+
table thead tr th {
47+
position: sticky;
48+
top: 0;
49+
z-index: 6;
50+
background: white;
51+
}
52+
td:first-child {
53+
position: sticky;
54+
left: 0;
55+
z-index: 5;
56+
background: white;
57+
}
58+
</style>
59+
<body>"##,
60+
&header,
61+
table,
62+
"</body></html>",
63+
];
64+
out.join("\n")
65+
}
66+
67+
fn who_has_what_peripherals(parts: &[Part]) -> BTreeMap<(String, u64), Vec<String>> {
68+
let mut peripherals: BTreeMap<(String, u64), Vec<String>> = BTreeMap::new();
69+
for part in parts {
70+
for periph in &part.device.peripherals {
71+
let name = (periph.name.clone(), periph.base_address);
72+
peripherals.entry(name).or_default().push(part.name.clone());
73+
}
74+
}
75+
peripherals
76+
}
77+
78+
fn html_table_peripherals(
79+
parts: &[Part],
80+
peripherals: &BTreeMap<(String, u64), Vec<String>>,
81+
) -> String {
82+
let mut out = "<table><thead><tr><th>Peripheral</th><th>Address</th>\n".to_string();
83+
for part in parts {
84+
out.push_str(&format!("<th>{}</th>\n", part.device.name));
85+
}
86+
out.push_str("</thead><tbody>\n");
87+
for ((name, base), periph_parts) in peripherals {
88+
let base = format!("0x{base:08X}");
89+
let link = format!(r#"<a href="{name}_{base}.html">{name}</a>"#);
90+
out.push_str(&format!("<tr><td>{link}</td><td>{base}</td>\n"));
91+
for part in parts {
92+
if periph_parts.contains(&part.name) {
93+
out.push_str(r##"<td align=center bgcolor="#ccffcc">&#10004;</td>"##);
94+
} else {
95+
out.push_str(r##"<td align=center bgcolor="#ffcccc">&#10008;</td>"##);
96+
}
97+
out.push('\n');
98+
}
99+
out.push_str("</tr>\n");
100+
}
101+
out.push_str("</tr>\n");
102+
out.push_str("</tbody></table>\n");
103+
out
104+
}
105+
106+
fn who_has_what_peripheral_registers(
107+
parts: &[Part],
108+
peripheral: &(String, u64),
109+
) -> BTreeMap<(u32, String), Vec<String>> {
110+
let mut registers: BTreeMap<(u32, String), Vec<String>> = BTreeMap::new();
111+
for part in parts {
112+
for periph in &part.device.peripherals {
113+
if periph.name != peripheral.0 || periph.base_address != peripheral.1 {
114+
continue;
115+
}
116+
for reg in periph.all_registers() {
117+
let name = (reg.address_offset, reg.name.clone());
118+
registers.entry(name).or_default().push(part.name.clone());
119+
}
120+
}
121+
}
122+
registers
123+
}
124+
125+
fn html_table_registers(
126+
parts: &[Part],
127+
peripheral: &(String, u64),
128+
registers: &BTreeMap<(u32, String), Vec<String>>,
129+
) -> String {
130+
let mut out = "<table><thead><tr><th>Register</th><th>Offset</th>\n".to_string();
131+
for part in parts {
132+
out.push_str(&format!("<th>{}</th>\n", part.device.name));
133+
}
134+
out.push_str("</thead><tbody>\n");
135+
for ((offset, name), reg_parts) in registers {
136+
let offset = format!("0x{offset:04X}");
137+
let link = format!(
138+
r#"<a href="{}_0x{:08X}_{name}_{offset}.html">{name}</a>"#,
139+
peripheral.0, peripheral.1
140+
);
141+
out.push_str(&format!("<tr><td>{link}</td><td>{offset}</td>\n"));
142+
for part in parts {
143+
if reg_parts.contains(&part.name) {
144+
out.push_str(r##"<td align=center bgcolor="#ccffcc">&#10004;</td>"##);
145+
} else {
146+
out.push_str(r##"<td align=center bgcolor="#ffcccc">&#10008;</td>"##);
147+
}
148+
out.push('\n');
149+
}
150+
out.push_str("</tr>\n");
151+
}
152+
out.push_str("</tr>\n");
153+
out.push_str("</tbody></table>");
154+
out
155+
}
156+
157+
fn who_has_what_register_fields(
158+
parts: &[Part],
159+
peripheral: &(String, u64),
160+
register: &(u32, String),
161+
) -> BTreeMap<(u32, u32, String), Vec<String>> {
162+
let mut fields: BTreeMap<(u32, u32, String), Vec<String>> = BTreeMap::new();
163+
for part in parts {
164+
for periph in &part.device.peripherals {
165+
if periph.name != peripheral.0 || periph.base_address != peripheral.1 {
166+
continue;
167+
}
168+
for reg in periph.all_registers() {
169+
if reg.name != register.1 || reg.address_offset != register.0 {
170+
continue;
171+
}
172+
for field in reg.fields() {
173+
let name = (field.bit_offset(), field.bit_width(), field.name.clone());
174+
fields.entry(name).or_default().push(part.name.clone());
175+
}
176+
}
177+
}
178+
}
179+
return fields;
180+
}
181+
182+
fn html_table_fields(parts: &[Part], fields: &BTreeMap<(u32, u32, String), Vec<String>>) -> String {
183+
let mut out = "<table><thead><tr><th>Field</th><th>Offset</th><th>Width</th>\n".to_string();
184+
for part in parts {
185+
out.push_str(&format!("<th>{}</th>\n", part.device.name));
186+
}
187+
out.push_str("</thead><tbody>\n");
188+
for ((offset, width, name), field_parts) in fields {
189+
out.push_str(&format!(
190+
"<tr><td>{name}</td><td>{offset}</td><td>{width}</td>\n"
191+
));
192+
for part in parts {
193+
if field_parts.contains(&part.name) {
194+
out.push_str(r##"<td align=center bgcolor="#ccffcc">&#10004;</td>"##);
195+
} else {
196+
out.push_str(r##"<td align=center bgcolor="#ffcccc">&#10008;</td>"##);
197+
}
198+
out.push('\n');
199+
}
200+
out.push_str("</tr>\n");
201+
}
202+
out.push_str("</tr>\n");
203+
out.push_str("</tbody></table>");
204+
out
205+
}
206+
207+
fn html_tables(parts: &[Part]) -> HashMap<String, String> {
208+
let peripherals = who_has_what_peripherals(parts);
209+
let mut files = HashMap::new();
210+
let peripheral_table = html_table_peripherals(parts, &peripherals);
211+
let peripheral_title = "Compare peripherals";
212+
files.insert(
213+
"index.html".to_string(),
214+
html_page(peripheral_title, &peripheral_table),
215+
);
216+
for pname in peripherals.keys() {
217+
let registers = who_has_what_peripheral_registers(parts, pname);
218+
let register_table = html_table_registers(parts, pname, &registers);
219+
let register_title = format!("Registers In {} 0x{:08X}", pname.0, pname.1);
220+
let mut filename = format!("{}_0x{:08X}.html", pname.0, pname.1);
221+
files.insert(filename, html_page(&register_title, &register_table));
222+
for rname in registers.keys() {
223+
let fields = who_has_what_register_fields(parts, pname, rname);
224+
let field_table = html_table_fields(parts, &fields);
225+
let field_title = format!(
226+
"Fields In {}_{} (0x{:08X}, 0x{:04X})",
227+
pname.0, rname.1, pname.1, rname.0
228+
);
229+
filename = format!(
230+
"{}_0x{:08X}_{}_0x{:04X}.html",
231+
pname.0, pname.1, rname.1, rname.0
232+
);
233+
files.insert(filename, html_page(&field_title, &field_table));
234+
}
235+
}
236+
files
237+
}
238+
239+
pub fn htmlcompare(htmldir: &Path, svdfiles: &[PathBuf]) -> Result<()> {
240+
let parts = svdfiles
241+
.iter()
242+
.map(|p| parse(p))
243+
.collect::<Result<Vec<_>>>()?;
244+
let files = html_tables(&parts);
245+
std::fs::create_dir_all(htmldir)?;
246+
for file in files {
247+
let f = htmldir.join(file.0);
248+
let mut f = File::create(f)?;
249+
f.write_all(file.1.as_bytes())?;
250+
}
251+
Ok(())
252+
}

src/html/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
pub mod html_cli;
2+
pub mod htmlcompare_cli;

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use std::{fs::File, path::Path, str::FromStr};
44

55
pub mod common;
66
pub mod convert;
7+
pub mod html;
78
pub mod interrupts;
89
pub mod makedeps;
910
pub mod mmap;

0 commit comments

Comments
 (0)