safety_parser/
configuration.rs1use indexmap::IndexMap;
3use serde::Deserialize;
4use std::{
5 env::{self, var},
6 fs,
7 sync::LazyLock,
8};
9
10pub type Str = Box<str>;
11pub type OptStr = Option<Box<str>>;
12
13#[derive(Debug, Deserialize)]
14pub struct Configuration {
15 pub package: Package,
16 pub tag: IndexMap<Str, Tag>,
17}
18
19impl Configuration {
20 pub fn read_toml(path: &str) -> Self {
21 if !fs::exists(path).unwrap() {
22 panic!("{path:?} doesn't exist.")
23 }
24 let text =
25 &fs::read_to_string(path).unwrap_or_else(|e| panic!("Failed to read {path}:\n{e}"));
26 toml::from_str(text).unwrap_or_else(|e| panic!("Failed to parse {path}:\n{e}"))
27 }
28}
29
30#[derive(Debug, Deserialize)]
31pub struct Package {
32 pub name: Box<str>,
33 pub version: OptStr,
34 pub crate_name: OptStr,
35}
36
37#[derive(Debug, Deserialize)]
38pub struct Tag {
39 pub args: Box<[Str]>,
40 pub desc: OptStr,
41 pub expr: OptStr,
42 #[serde(default = "default_types")]
43 pub types: Box<[TagType]>,
44 pub url: OptStr,
45}
46
47#[derive(Clone, Copy, Debug, Deserialize, Default, PartialEq, Eq)]
48#[serde(rename_all = "lowercase")]
49pub enum TagType {
50 #[default]
51 Precond,
52 Hazard,
53 Option,
54}
55
56impl TagType {
57 pub fn new(s: &str) -> Self {
58 match s {
59 "precond" => Self::Precond,
60 "hazard" => Self::Hazard,
61 "option" => Self::Option,
62 _ => panic!("Only support: precond, hazard, and option."),
63 }
64 }
65}
66
67fn default_types() -> Box<[TagType]> {
69 Box::new([TagType::Precond])
70}
71
72pub const ENV_SP_FILE: &str = "SP_FILE";
74pub const ENV_SP_DIR: &str = "SP_DIR";
76
77pub fn config_exists() -> bool {
80 static EMIT: LazyLock<bool> =
81 LazyLock::new(|| var(ENV_SP_FILE).is_ok() || var(ENV_SP_DIR).is_ok());
82 *EMIT
83}
84
85pub fn toml_file_paths() -> Vec<String> {
90 if let Ok(file) = env::var(ENV_SP_FILE) {
91 vec![file]
92 } else if let Ok(dir) = env::var(ENV_SP_DIR) {
93 let mut files = Vec::new();
94 for entry in
95 fs::read_dir(&dir).unwrap_or_else(|e| panic!("Failed to read {dir} folder:\n{e}"))
96 {
97 let entry = entry.unwrap();
98 let path = entry.path();
99 if path.extension().map(|ext| ext == "toml").unwrap_or(false) {
100 files.push(path.into_os_string().into_string().unwrap());
101 }
102 }
103 files
104 } else {
105 panic!("Environment variable `SP_FILE` or `SP_DIR` should be specified.");
106 }
107}
108
109#[derive(Debug)]
111pub struct Key {
112 pub tag: Tag,
114 pub src: Str,
117}
118
119pub static TAGS: LazyLock<IndexMap<Str, Key>> = LazyLock::new(|| {
120 let configs: Vec<_> =
121 toml_file_paths().into_iter().map(|f| (Configuration::read_toml(&f), f)).collect();
122 let cap = configs.iter().map(|c| c.0.tag.len()).sum();
123 let mut map = IndexMap::with_capacity(cap);
124 for (config, path) in configs {
125 for (name, tag) in config.tag {
126 if let Some(old) = map.get(&name) {
127 panic!("Tag {name:?} has been defined: {old:?}");
128 }
129 _ = map.insert(name, Key { tag, src: (&*path).into() });
130 }
131 }
132 map.sort_unstable_keys();
133 eprintln!("Got {} tags.", map.len());
134 map
135});
136
137pub fn get_tag(name: &str) -> &'static Tag {
138 &TAGS.get(name).unwrap_or_else(|| panic!("Tag {name:?} is not defined")).tag
139}