|
| 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">✔</td>"##); |
| 94 | + } else { |
| 95 | + out.push_str(r##"<td align=center bgcolor="#ffcccc">✘</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">✔</td>"##); |
| 145 | + } else { |
| 146 | + out.push_str(r##"<td align=center bgcolor="#ffcccc">✘</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">✔</td>"##); |
| 195 | + } else { |
| 196 | + out.push_str(r##"<td align=center bgcolor="#ffcccc">✘</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, ®isters); |
| 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(®ister_title, ®ister_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 | +} |
0 commit comments