1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
#[macro_export]
macro_rules! count {
() => (0usize);
($x:tt $($xs:tt)*) => (1usize + crate::count!($($xs)*));
}
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct Choice {
pub(crate) id: &'static str,
pub(crate) description: Option<&'static str>,
pub(crate) exclusive_files: Option<&'static [&'static str]>,
}
#[macro_export]
macro_rules! choice {
(
$(
$(#[$meta_attr:meta])*
$vis:vis enum $name:ident {
$(
$variant:ident = $meta:expr
),* $(,)?
}
)+
) => {
$(
#[repr(usize)]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
$(#[$meta_attr])*
$vis enum $name {
$(
$variant,
)*
}
impl $name {
#[allow(dead_code)]
$vis const VARIANTS: [&'static str; crate::count!($($variant)*)] = [
$($meta.id),*
];
#[allow(dead_code)]
$vis const ALL_FILES: &'static [&'static str] = {
const fn count_files() -> usize {
let mut count = 0;
$(
if let Some(files) = $meta.exclusive_files {
count += files.len();
}
)*
count
}
const LEN: usize = count_files();
const fn collect_files() -> [&'static str; LEN] {
let mut result = [""; LEN];
let mut i = 0;
$(
if let Some(files) = $meta.exclusive_files {
let mut j = 0;
while j < files.len() {
result[i] = files[j];
i += 1;
j += 1;
}
}
)*
result
}
&collect_files()
};
const METADATA: &'static [Choice] = &[
$($meta),*
];
$vis fn metadata(&self) -> &'static Choice {
match self {
$(
Self::$variant => &Self::METADATA[*self as usize],
)*
}
}
#[allow(dead_code)]
$vis fn all_other_files(&self) -> Vec<&'static str> {
let self_files = self.metadata().exclusive_files.unwrap_or_default();
let mut all_files = Vec::new();
for metadata in Self::METADATA.iter() {
if let Some(files) = &metadata.exclusive_files {
for &file in files.iter() {
if !self_files.contains(&file) {
all_files.push(file);
}
}
}
}
all_files
}
#[allow(dead_code)]
$vis fn get_variant_from_exclusive_file(file_name: &str) -> Option<&'static str> {
for metadata in Self::METADATA.iter() {
if let Some(files) = &metadata.exclusive_files {
if files.contains(&file_name) {
return Some(metadata.id);
}
}
}
None
}
}
impl ToString for $name {
fn to_string(&self) -> String {
self.metadata().id.to_string()
}
}
impl std::str::FromStr for $name {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
$(
s if s == $meta.id => Ok(Self::$variant),
)*
_ => anyhow::bail!("Invalid {}: {}", stringify!($name), s)
}
}
}
)+
}
}