use std::collections::BTreeSet;
use std::fs;
use std::path::Path;
const SKIP: &[&str] = &[
"Error",
"AnyApdu",
"ApplicationInfoV2Apdu",
"ApplicationMmiApdu",
"BroadcastServiceGatewayApdu",
"CaPipelineApdu",
"CaSupportApdu",
"CiExtApdu",
"CiPlusApdu",
"CicamPlayerApdu",
"ContentControlApdu",
"CopyProtectionApdu",
"DownloadApdu",
"EventManagerApdu",
"FileRetrievalApdu",
"LscV4Apdu",
"LscV4ReplyApdu",
"MultistreamApdu",
"MultistreamHostControlApdu",
"PowerManagerApdu",
"ResourceManagerV2Apdu",
"SampleDecryptionApdu",
"ServiceGatewayApdu",
"StatusQueryApdu",
"StreamInputApdu",
"ControlCommand",
"EmiData",
"CommsCmdParams",
"DisplayReplyBody",
"SamplePayload",
];
fn read_rs(dir: &Path, out: &mut Vec<(String, String)>) {
for entry in fs::read_dir(dir).expect("read src dir") {
let path = entry.expect("dir entry").path();
if path.is_dir() {
read_rs(&path, out);
} else if path.extension().is_some_and(|x| x == "rs") {
let text = fs::read_to_string(&path).expect("read .rs");
out.push((path.display().to_string(), text));
}
}
}
fn has_impl(src: &str, prefix: &str, name: &str) -> bool {
let needle = format!("{prefix}{name}");
let is_boundary =
|rest: &str| !matches!(rest.chars().next(), Some(c) if c.is_alphanumeric() || c == '_');
let is_path_or_space =
|c: char| c.is_whitespace() || c == ':' || c.is_alphanumeric() || c == '_';
fn strip_path_qualifier(s: &str) -> &str {
let mut rest = s;
loop {
let ident_len = rest
.chars()
.take_while(|c| c.is_alphanumeric() || *c == '_')
.count();
if ident_len == 0 {
break;
}
match rest[ident_len..].strip_prefix("::") {
Some(after) => rest = after,
None => break,
}
}
rest
}
for line in src.lines() {
let trimmed = line.trim_start();
if let Some(rest) = strip_path_qualifier(trimmed).strip_prefix(&needle)
&& is_boundary(rest)
{
return true;
}
if !needle.starts_with("impl")
&& let Some(after_impl) = trimmed.strip_prefix("impl")
&& let Some(pos) = after_impl.find(&needle)
{
let qualifier = &after_impl[..pos];
let rest = &after_impl[pos + needle.len()..];
if qualifier.chars().all(is_path_or_space) && is_boundary(rest) {
return true;
}
}
}
false
}
fn pub_enums(src: &str) -> BTreeSet<String> {
let mut enums = BTreeSet::new();
for line in src.lines() {
if let Some(rest) = line.trim_start().strip_prefix("pub enum ") {
let name: String = rest
.chars()
.take_while(|c| c.is_alphanumeric() || *c == '_')
.collect();
if !name.is_empty() {
enums.insert(name);
}
}
}
enums
}
fn crate_files() -> Vec<(String, String)> {
let src = Path::new(env!("CARGO_MANIFEST_DIR")).join("src");
let mut files = Vec::new();
read_rs(&src, &mut files);
files
}
#[test]
fn every_public_spec_enum_has_a_display_impl() {
let mut missing: Vec<String> = Vec::new();
for (path, src) in &crate_files() {
for e in pub_enums(src) {
if SKIP.contains(&e.as_str()) {
continue;
}
let labelled = has_impl(src, "impl_spec_display!(", &e)
|| has_impl(src, "impl ::core::fmt::Display for ", &e)
|| has_impl(src, "impl core::fmt::Display for ", &e)
|| has_impl(src, "impl std::fmt::Display for ", &e)
|| has_impl(src, "impl fmt::Display for ", &e);
if !labelled {
missing.push(format!("{e} (in {path})"));
}
}
}
assert!(
missing.is_empty(),
"public enum(s) missing a #204 Display/name() label (or a SKIP-list entry): {missing:?}"
);
}