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: Option<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: Str,
33 pub version: OptStr,
34 pub crate_name: OptStr,
35}
36
37#[derive(Debug, Deserialize)]
38pub struct Tag {
39 #[serde(default)]
40 pub args: Box<[Str]>,
41 pub desc: OptStr,
42 pub expr: OptStr,
43 #[serde(default = "default_types")]
44 pub types: Box<[TagType]>,
45 pub url: OptStr,
46}
47
48#[derive(Clone, Copy, Debug, Deserialize, Default, PartialEq, Eq)]
49#[serde(rename_all = "lowercase")]
50pub enum TagType {
51 #[default]
52 Precond,
53 Hazard,
54 Option,
55}
56
57impl TagType {
58 pub fn new(s: &str) -> Self {
59 match s {
60 "precond" => Self::Precond,
61 "hazard" => Self::Hazard,
62 "option" => Self::Option,
63 _ => panic!("Only support: precond, hazard, and option."),
64 }
65 }
66}
67
68fn default_types() -> Box<[TagType]> {
70 Box::new([TagType::Precond])
71}
72
73pub const ENV_SP_FILE: &str = "SP_FILE";
75pub const ENV_SP_DIR: &str = "SP_DIR";
77
78pub fn config_exists() -> bool {
81 static EMIT: LazyLock<bool> =
82 LazyLock::new(|| var(ENV_SP_FILE).is_ok() || var(ENV_SP_DIR).is_ok());
83 *EMIT
84}
85
86pub fn toml_file_paths() -> Vec<String> {
91 if let Ok(file) = env::var(ENV_SP_FILE) {
92 vec![file]
93 } else if let Ok(dir) = env::var(ENV_SP_DIR) {
94 let mut files = Vec::new();
95 for entry in
96 fs::read_dir(&dir).unwrap_or_else(|e| panic!("Failed to read {dir} folder:\n{e}"))
97 {
98 let entry = entry.unwrap();
99 let path = entry.path();
100 if path.extension().map(|ext| ext == "toml").unwrap_or(false) {
101 files.push(path.into_os_string().into_string().unwrap());
102 }
103 }
104 files
105 } else {
106 panic!("Environment variable `SP_FILE` or `SP_DIR` should be specified.");
107 }
108}
109
110#[derive(Debug)]
112pub struct Key {
113 pub tag: Tag,
115 pub src: Str,
118}
119
120pub static TAGS: LazyLock<IndexMap<Str, Key>> = LazyLock::new(|| {
121 let configs: Vec<_> =
122 toml_file_paths().into_iter().map(|f| (Configuration::read_toml(&f), f)).collect();
123 let cap = configs.iter().map(|c| c.0.tag.len()).sum();
124 let mut map = IndexMap::with_capacity(cap);
125 for (config, path) in configs {
126 for (name, tag) in config.tag {
127 if let Some(old) = map.get(&name) {
128 panic!("Tag {name:?} has been defined: {old:?}");
129 }
130 _ = map.insert(name, Key { tag, src: (&*path).into() });
131 }
132 }
133 map.sort_unstable_keys();
134 eprintln!("Got {} tags.", map.len());
135 map
136});
137
138pub fn get_tag(name: &str) -> &'static Tag {
139 &TAGS.get(name).unwrap_or_else(|| panic!("Tag {name:?} is not defined")).tag
140}