compatdb_core/table.rs
1//! The table of rules, each with where it comes from.
2
3#[cfg(doc)]
4use crate::{ENV_VAR, Origin};
5use crate::{Exe, Resolution, Rule, Specificity, VersionInfo, builtin};
6
7/// Where a rule of a [`Table`] comes from. It decides how the log names the
8/// rule's values (see [`Origin`]) and whether its `env` entries are defaults
9/// (see [`Origin::env_is_default`]). Never serialized.
10#[derive(Clone, Copy, Debug, PartialEq, Eq)]
11pub enum Source {
12 /// Compiled into `compatdb.so`: a rule of [`Table::builtin`].
13 BuiltIn,
14 /// Line `n` of [`ENV_VAR`], counted from 1 as [`Table::parse_lenient`]
15 /// reads the value, blank lines and a legacy header included.
16 Line(usize),
17}
18
19/// The whole database: the built-in rules followed by the rules of
20/// [`ENV_VAR`], in the order they were declared, each with its [`Source`].
21///
22/// A launcher builds the rules it sends with [`Table::new`] and
23/// [`Table::push`] and writes them with `to_string()`, which is the value of
24/// [`ENV_VAR`]; [`Table::with_builtin`] puts the built-in rules in front, as
25/// `compatdb.so` does, to see what a process would get.
26#[derive(Clone, Default, Debug, PartialEq, Eq)]
27pub struct Table {
28 /// The rules, in the order they were declared, which is the order
29 /// [`Table::resolve`] folds rules of equal place in its order in.
30 pub(crate) rules: Vec<Rule>,
31 /// Where each rule comes from, at the same index.
32 pub(crate) sources: Vec<Source>,
33}
34
35impl Table {
36 /// The table without rules.
37 #[must_use]
38 pub const fn new() -> Self {
39 Self {
40 rules: Vec::new(),
41 sources: Vec::new(),
42 }
43 }
44
45 /// The table of the built-in rules ([`builtin::rules`]), which
46 /// `compatdb.so` puts in front of the rules of [`ENV_VAR`].
47 #[must_use]
48 pub fn builtin() -> Self {
49 Self::of_builtin(builtin::rules())
50 }
51
52 /// The table of `rules`, each a built-in rule.
53 pub(crate) fn of_builtin(rules: Vec<Rule>) -> Self {
54 Self {
55 sources: vec![Source::BuiltIn; rules.len()],
56 rules,
57 }
58 }
59
60 /// This table with the built-in rules in front of its own, as
61 /// `compatdb.so` builds its table.
62 #[must_use]
63 pub fn with_builtin(self) -> Self {
64 let mut table = Self::builtin();
65 table.append(self);
66 table
67 }
68
69 /// Add the rules of `other` after these, with their sources.
70 pub(crate) fn append(&mut self, other: Self) {
71 self.rules.extend(other.rules);
72 self.sources.extend(other.sources);
73 }
74
75 /// Add `rule` after the others as the rule of line `line` of
76 /// [`ENV_VAR`].
77 pub(crate) fn push_line(&mut self, rule: Rule, line: usize) {
78 self.rules.push(rule);
79 self.sources.push(Source::Line(line));
80 }
81
82 /// Add `rule` after the others, as a rule of [`ENV_VAR`] on the line
83 /// after the last line of the table, which for a table built with this
84 /// method alone is its line in the value the table writes.
85 pub fn push(&mut self, rule: Rule) {
86 let last = self
87 .sources
88 .iter()
89 .filter_map(|source| match source {
90 Source::Line(n) => Some(*n),
91 Source::BuiltIn => None,
92 })
93 .max()
94 .unwrap_or(0);
95 self.push_line(rule, last.saturating_add(1));
96 }
97
98 /// The rules, in the order they were declared.
99 #[must_use]
100 pub fn rules(&self) -> &[Rule] {
101 &self.rules
102 }
103
104 /// Where each rule of [`Table::rules`] comes from, at the same index.
105 #[must_use]
106 pub fn sources(&self) -> &[Source] {
107 &self.sources
108 }
109
110 /// Each rule with its source, in the order they were declared.
111 pub(crate) fn entries(&self) -> impl Iterator<Item = (Source, &Rule)> {
112 self.sources.iter().copied().zip(&self.rules)
113 }
114
115 /// What a process with the image basename `exe` and the version
116 /// resource `version` would get from `compatdb.so` with this table in
117 /// [`ENV_VAR`]: the built-in rules followed by this table's own (a
118 /// built-in rule in this table is left out, since the library has it
119 /// already), resolved, with [`Resolution::fold_derived_env`] done.
120 ///
121 /// Which env entries apply also depends on the environment the process
122 /// starts with, which [`Resolution::env_steps`] takes.
123 #[must_use]
124 pub fn preview(&self, exe: &str, version: &VersionInfo) -> Resolution {
125 let mut table = Self::builtin();
126 for (source, rule) in self.entries() {
127 if source != Source::BuiltIn {
128 table.rules.push(rule.clone());
129 table.sources.push(source);
130 }
131 }
132 let mut resolution = table.resolve(exe, version);
133 resolution.fold_derived_env();
134 resolution
135 }
136
137 /// The `WINEDEBUG` entries the `debug_channels` of this table's rules
138 /// stand for, in table order. A launcher joins them with `,` after its
139 /// own entries and starts Wine with the result, since Wine reads the
140 /// variable before `compatdb.so` loads.
141 ///
142 /// An entry of a rule for an executable is scoped to it as
143 /// `<exe>:<entry>`, which is Wine's own syntax: Wine compares the prefix
144 /// with the image file name ASCII-case-insensitively and knows nothing
145 /// of fingerprints, so the entry reaches every program of that name. An
146 /// entry of a rule with `exe` `*` is unscoped. Wine lets a later entry
147 /// win for the same channel.
148 #[must_use]
149 pub fn winedebug(&self) -> Vec<String> {
150 self.rules
151 .iter()
152 .flat_map(|rule| {
153 rule.debug_channels
154 .iter()
155 .map(move |channel| match &rule.exe {
156 Exe::Any => channel.to_string(),
157 Exe::Name(name) => format!("{name}:{channel}"),
158 })
159 })
160 .collect()
161 }
162
163 /// Every pair of rules that matches exactly the same processes, by their
164 /// index in the table. A duplicate can only be a mistake, so the
165 /// launcher warns about it.
166 ///
167 /// Plain wildcard rules (`exe = *` with no fingerprint) are left out: they
168 /// are defaults that are meant to stack, one per setting, so several of
169 /// them matching every process is not a mistake.
170 #[must_use]
171 pub fn duplicate_matchers(&self) -> Vec<(usize, usize)> {
172 let mut pairs = Vec::new();
173 for (i, rule) in self.rules.iter().enumerate() {
174 if rule.specificity() == Specificity::Any {
175 continue;
176 }
177 for (j, other) in self.rules.iter().enumerate().skip(i.saturating_add(1)) {
178 if rule.matches_the_same_as(other) {
179 pairs.push((i, j));
180 }
181 }
182 }
183 pairs
184 }
185}
186
187#[cfg(test)]
188mod tests;