safety_parser/configuration/
mod.rs1use indexmap::IndexMap;
3use serde::Deserialize;
4use std::{fs, sync::LazyLock};
5
6pub mod env;
7
8pub type Str = Box<str>;
9pub type OptStr = Option<Box<str>>;
10
11#[derive(Debug, Deserialize)]
12pub struct Configuration {
13 pub package: Option<Package>,
14 pub tag: IndexMap<Str, Tag>,
15 #[serde(default)]
16 pub doc: GenDocOption,
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 pub fn as_str(&self) -> &'static str {
68 match self {
69 TagType::Precond => "precond",
70 TagType::Hazard => "Hazard",
71 TagType::Option => "option",
72 }
73 }
74}
75
76fn default_types() -> Box<[TagType]> {
78 Box::new([TagType::Precond])
79}
80
81#[derive(Clone, Copy, Debug, Deserialize, Default)]
82pub struct GenDocOption {
83 #[serde(default)]
85 pub heading_safety_title: bool,
86 #[serde(default)]
88 pub heading_tag: bool,
89}
90
91impl GenDocOption {
92 fn merge(&mut self, other: &Self) {
93 if other.heading_safety_title {
94 self.heading_safety_title = true;
95 }
96 if other.heading_tag {
97 self.heading_tag = true;
98 }
99 }
100}
101
102pub const ANY: &str = "any";
104
105#[derive(Debug)]
107struct Key {
108 tag: Tag,
110 #[allow(dead_code)]
113 src: Str,
114}
115
116#[derive(Default)]
117struct Cache {
118 map: IndexMap<Str, Key>,
120 doc: GenDocOption,
122}
123
124static CACHE: LazyLock<Cache> = LazyLock::new(|| {
125 let mut cache = Cache::default();
126
127 let configs: Vec<_> = env::toml_file_paths()
128 .into_iter()
129 .map(|f| (Configuration::read_toml(&f), f.into_boxed_str()))
130 .collect();
131 let cap = configs.iter().map(|c| c.0.tag.len()).sum();
132 cache.map.reserve(cap);
133
134 for (config, path) in configs {
135 for (name, tag) in config.tag {
136 if &*name == ANY {
137 panic!("`any` is a builtin tag. Please remove it from spec.");
138 }
139 if let Some(old) = cache.map.get(&name) {
140 panic!("Tag {name:?} has been defined: {old:?}");
141 }
142 _ = cache.map.insert(name, Key { tag, src: path.clone() });
143 }
144 cache.doc.merge(&config.doc);
145 }
146
147 cache.map.sort_unstable_keys();
148 eprintln!("Got {} tags.", cache.map.len());
149 cache
150});
151
152pub fn get_tag(name: &str) -> &'static Tag {
153 &CACHE.map.get(name).unwrap_or_else(|| panic!("Tag {name:?} is not defined")).tag
154}
155
156pub fn get_tag_opt(name: &str) -> Option<&'static Tag> {
157 CACHE.map.get(name).map(|val| &val.tag)
158}
159
160pub fn doc_option() -> GenDocOption {
161 CACHE.doc
162}
163
164pub struct DefinedTag {
165 pub name: &'static str,
166 pub args: &'static Tag,
167}
168
169impl DefinedTag {
170 pub fn hover_detail(&self) -> String {
171 let name = self.name;
172 let args = &*self.args.args;
173 if args.is_empty() {
174 name.to_owned()
175 } else {
176 let args = args.join(", ");
177 format!("{name}({args})")
178 }
179 }
180
181 pub fn hover_documentation(&self) -> String {
182 use std::fmt::Write;
183
184 let DefinedTag { args: Tag { desc, expr, types, url, .. }, .. } = self;
185 let mut doc = String::new();
186
187 let types_field = if types.len() == 1 { "type" } else { "types" };
188 let types = types.iter().map(|t| t.as_str()).collect::<Vec<_>>().join(", ");
189 _ = writeln!(&mut doc, "**{types_field}**: {types}\n");
190
191 if let Some(desc) = desc {
192 _ = writeln!(&mut doc, "**desc**: {desc}\n");
193 }
194 if let Some(expr) = expr {
195 _ = writeln!(&mut doc, "**expr**: {expr}\n");
196 }
197 if let Some(url) = url {
198 _ = writeln!(&mut doc, "**url**: <{url}>");
199 }
200 doc
201 }
202}
203
204pub fn get_tags() -> Box<[DefinedTag]> {
206 CACHE.map.iter().map(|(k, v)| DefinedTag { name: k, args: &v.tag }).collect()
207}