safety_parser/configuration/
mod.rs1use indexmap::IndexMap;
3use serde::{Deserialize, Serialize};
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(Clone, Debug, Deserialize, Serialize)]
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, Serialize, 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, Serialize, 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(Clone, Debug, Deserialize, Serialize)]
107pub struct Key {
108 pub tag: Tag,
110 pub src: Str,
113}
114
115#[derive(Clone, Debug, Default, Deserialize, Serialize)]
116pub struct Cache {
117 pub map: IndexMap<Str, Key>,
119 pub doc: GenDocOption,
121}
122
123pub static CACHE: LazyLock<Cache> = LazyLock::new(|| {
124 let mut cache = Cache::default();
125
126 let configs: Vec<_> = env::toml_file_paths()
127 .into_iter()
128 .map(|f| (Configuration::read_toml(&f), f.into_boxed_str()))
129 .collect();
130 let cap = configs.iter().map(|c| c.0.tag.len()).sum();
131 cache.map.reserve(cap);
132
133 for (config, path) in configs {
134 for (name, tag) in config.tag {
135 if &*name == ANY {
136 panic!("`any` is a builtin tag. Please remove it from spec.");
137 }
138 if let Some(old) = cache.map.get(&name) {
139 panic!("Tag {name:?} has been defined: {old:?}");
140 }
141 _ = cache.map.insert(name, Key { tag, src: path.clone() });
142 }
143 cache.doc.merge(&config.doc);
144 }
145
146 cache.map.sort_unstable_keys();
147 eprintln!("Got {} tags.", cache.map.len());
148 cache
149});
150
151pub fn get_tag(name: &str) -> &'static Tag {
152 &CACHE.map.get(name).unwrap_or_else(|| panic!("Tag {name:?} is not defined")).tag
153}
154
155pub fn get_tag_opt(name: &str) -> Option<&'static Tag> {
156 CACHE.map.get(name).map(|val| &val.tag)
157}
158
159pub fn doc_option() -> GenDocOption {
160 CACHE.doc
161}
162
163pub struct DefinedTag {
164 pub name: &'static str,
165 pub args: &'static Tag,
166}
167
168impl DefinedTag {
169 pub fn hover_detail(&self) -> String {
170 let name = self.name;
171 let args = &*self.args.args;
172 if args.is_empty() {
173 name.to_owned()
174 } else {
175 let args = args.join(", ");
176 format!("{name}({args})")
177 }
178 }
179
180 pub fn hover_documentation(&self) -> String {
181 use std::fmt::Write;
182
183 let DefinedTag { args: Tag { desc, expr, types, url, .. }, .. } = self;
184 let mut doc = String::new();
185
186 let types_field = if types.len() == 1 { "type" } else { "types" };
187 let types = types.iter().map(|t| t.as_str()).collect::<Vec<_>>().join(", ");
188 _ = writeln!(&mut doc, "**{types_field}**: {types}\n");
189
190 if let Some(desc) = desc {
191 _ = writeln!(&mut doc, "**desc**: {desc}\n");
192 }
193 if let Some(expr) = expr {
194 _ = writeln!(&mut doc, "**expr**: {expr}\n");
195 }
196 if let Some(url) = url {
197 _ = writeln!(&mut doc, "**url**: <{url}>");
198 }
199 doc
200 }
201}
202
203pub fn get_tags() -> Box<[DefinedTag]> {
205 CACHE.map.iter().map(|(k, v)| DefinedTag { name: k, args: &v.tag }).collect()
206}