Skip to main content

compatdb_core/
part.rs

1//! The typed parts of a rule: its `exe` and fingerprint values, and the
2//! entries of its lists. Each part checks its text when it is made, so every
3//! rule that can be built can also be written to [`ENV_VAR`] and read back.
4//!
5//! No part holds a NUL: the process APIs that take these texts end a string
6//! at the first one.
7
8use std::{fmt, str::FromStr};
9
10use crate::{DEFAULT_VALUE, FieldError};
11#[cfg(doc)]
12use crate::{ENV_VAR, Rule};
13
14/// `text` itself, or [`FieldError::Nul`] when it holds a NUL.
15fn without_nul(text: String) -> Result<String, FieldError> {
16    if text.contains('\0') {
17        Err(FieldError::Nul)
18    } else {
19        Ok(text)
20    }
21}
22
23/// The executable a rule matches: every process, or the processes whose image
24/// basename is a name, compared ASCII-case-insensitively.
25#[derive(Clone, Debug, PartialEq, Eq, Hash)]
26pub enum Exe {
27    /// `*`: every process.
28    Any,
29    /// The processes with this image basename.
30    Name(ExeName),
31}
32
33/// The image basename of an [`Exe::Name`]: not empty, not `*` and without a
34/// NUL. [`Exe::new`] makes one.
35#[derive(Clone, Debug, PartialEq, Eq, Hash)]
36pub struct ExeName(String);
37
38impl Exe {
39    /// The spelling of [`Exe::Any`].
40    pub const ANY: &'static str = "*";
41
42    /// The `exe` that `text` names: `*` is [`Exe::Any`], anything else the
43    /// basename as written.
44    ///
45    /// # Errors
46    ///
47    /// [`FieldError::MissingExe`] for an empty `text`, [`FieldError::Nul`]
48    /// for one with a NUL.
49    pub fn new(text: impl Into<String>) -> Result<Self, FieldError> {
50        let text = without_nul(text.into())?;
51        match text.as_str() {
52            "" => Err(FieldError::MissingExe),
53            Self::ANY => Ok(Self::Any),
54            _ => Ok(Self::Name(ExeName(text))),
55        }
56    }
57
58    /// `text` as an `exe`, unchecked: only for the literals of this crate,
59    /// which a test reads back.
60    pub(crate) fn literal(text: &str) -> Self {
61        if text == Self::ANY {
62            Self::Any
63        } else {
64            Self::Name(ExeName(text.to_owned()))
65        }
66    }
67
68    /// `*` or the basename, as the wire writes it.
69    #[must_use]
70    pub fn as_str(&self) -> &str {
71        match self {
72            Self::Any => Self::ANY,
73            Self::Name(name) => name.as_str(),
74        }
75    }
76
77    /// Whether this is [`Exe::Any`].
78    #[must_use]
79    pub const fn is_any(&self) -> bool {
80        matches!(self, Self::Any)
81    }
82
83    /// Whether a process with the image basename `basename` matches.
84    #[must_use]
85    pub fn matches(&self, basename: &str) -> bool {
86        match self {
87            Self::Any => true,
88            Self::Name(name) => basename.eq_ignore_ascii_case(name.as_str()),
89        }
90    }
91}
92
93impl ExeName {
94    /// The basename as written.
95    #[must_use]
96    pub fn as_str(&self) -> &str {
97        &self.0
98    }
99}
100
101impl FromStr for Exe {
102    type Err = FieldError;
103
104    /// See [`Exe::new`].
105    fn from_str(s: &str) -> Result<Self, Self::Err> {
106        Self::new(s)
107    }
108}
109
110impl fmt::Display for Exe {
111    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
112        f.write_str(self.as_str())
113    }
114}
115
116impl fmt::Display for ExeName {
117    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
118        f.write_str(self.as_str())
119    }
120}
121
122impl PartialEq<&str> for Exe {
123    fn eq(&self, other: &&str) -> bool {
124        self.as_str() == *other
125    }
126}
127
128/// How specific a rule's matchers are, least specific first (see
129/// [`Table::resolve`](crate::Table::resolve) for how it orders the rules
130/// with their source).
131#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
132pub enum Specificity {
133    /// `exe` `*` and no fingerprint: a default for every process.
134    Any,
135    /// `exe` `*` with a fingerprint.
136    AnyWithFingerprint,
137    /// An executable name without a fingerprint.
138    Exe,
139    /// An executable name with a fingerprint.
140    ExeWithFingerprint,
141}
142
143/// A fingerprint value: a substring that one string of the image's version
144/// resource must contain, compared ASCII-case-insensitively. Any text
145/// without a NUL; an empty one matches every image.
146#[derive(Clone, Debug, PartialEq, Eq, Hash)]
147pub struct Fingerprint(String);
148
149impl Fingerprint {
150    /// `text` as a fingerprint value.
151    ///
152    /// # Errors
153    ///
154    /// [`FieldError::Nul`] when `text` holds a NUL.
155    pub fn new(text: impl Into<String>) -> Result<Self, FieldError> {
156        without_nul(text.into()).map(Self)
157    }
158
159    /// `text` as a fingerprint value, unchecked: only for the literals of
160    /// this crate, which a test reads back.
161    pub(crate) fn literal(text: &str) -> Self {
162        Self(text.to_owned())
163    }
164
165    /// The value as written.
166    #[must_use]
167    pub fn as_str(&self) -> &str {
168        &self.0
169    }
170
171    /// Whether the version string `actual` contains this value, compared
172    /// ASCII-case-insensitively.
173    #[must_use]
174    pub fn matches(&self, actual: &str) -> bool {
175        actual
176            .to_ascii_lowercase()
177            .contains(&self.0.to_ascii_lowercase())
178    }
179}
180
181/// Text appended to the command line: not empty and without a NUL.
182#[derive(Clone, Debug, PartialEq, Eq, Hash)]
183pub struct Argument(String);
184
185impl Argument {
186    /// `text` as an argument.
187    ///
188    /// # Errors
189    ///
190    /// [`FieldError::EmptyArgument`] for an empty `text`,
191    /// [`FieldError::Nul`] for one with a NUL.
192    pub fn new(text: impl Into<String>) -> Result<Self, FieldError> {
193        let text = without_nul(text.into())?;
194        if text.is_empty() {
195            Err(FieldError::EmptyArgument)
196        } else {
197            Ok(Self(text))
198        }
199    }
200
201    /// `text` as an argument, unchecked: only for the literals of this
202    /// crate, which a test reads back.
203    pub(crate) fn literal(text: &str) -> Self {
204        Self(text.to_owned())
205    }
206
207    /// The text as written.
208    #[must_use]
209    pub fn as_str(&self) -> &str {
210        &self.0
211    }
212}
213
214/// One `WINEDEBUG` entry of a rule, such as `+relay` or `warn-heap`: not
215/// empty, without `,` and without a NUL. The text is kept as written; Wine
216/// reads it.
217///
218/// A `,` separates the entries of `WINEDEBUG`, so the text after one would
219/// be an entry of its own, which the `<exe>:` scope of
220/// [`Table::winedebug`](crate::Table::winedebug) does not reach.
221#[derive(Clone, Debug, PartialEq, Eq, Hash)]
222pub struct DebugChannel(String);
223
224impl DebugChannel {
225    /// `text` as a `WINEDEBUG` entry.
226    ///
227    /// # Errors
228    ///
229    /// [`FieldError::EmptyDebugChannel`] for an empty `text`,
230    /// [`FieldError::CommaInDebugChannel`] for one with a `,`,
231    /// [`FieldError::Nul`] for one with a NUL.
232    pub fn new(text: impl Into<String>) -> Result<Self, FieldError> {
233        let text = without_nul(text.into())?;
234        if text.is_empty() {
235            Err(FieldError::EmptyDebugChannel)
236        } else if text.contains(',') {
237            Err(FieldError::CommaInDebugChannel(text))
238        } else {
239            Ok(Self(text))
240        }
241    }
242
243    /// The entry as written.
244    #[must_use]
245    pub fn as_str(&self) -> &str {
246        &self.0
247    }
248}
249
250/// One `WINEDLLOVERRIDES` element, `<modules>=<order>`.
251///
252/// That is comma-separated module names, `=`, and a load order such as
253/// `n,b`, empty to disable the modules, or `default` to take them out of the
254/// overrides of the rules folded before. The text is kept as written.
255#[derive(Clone, Debug, PartialEq, Eq, Hash)]
256pub struct DllOverride(String);
257
258impl DllOverride {
259    /// `text` as a DLL override.
260    ///
261    /// # Errors
262    ///
263    /// [`FieldError::InvalidDllOverride`] when `text` has no `=`,
264    /// [`FieldError::Nul`] when it holds a NUL.
265    pub fn new(text: impl Into<String>) -> Result<Self, FieldError> {
266        let text = without_nul(text.into())?;
267        if text.contains('=') {
268            Ok(Self(text))
269        } else {
270            Err(FieldError::InvalidDllOverride(text))
271        }
272    }
273
274    /// `text` as a DLL override, unchecked: only for the literals of this
275    /// crate, which a test reads back, and for overrides made from the parts
276    /// of a checked one.
277    pub(crate) fn literal(text: &str) -> Self {
278        Self(text.to_owned())
279    }
280
281    /// The text as written, which is also its form in `WINEDLLOVERRIDES`.
282    #[must_use]
283    pub fn as_str(&self) -> &str {
284        &self.0
285    }
286
287    /// The module names before the `=`, trimmed, without empty ones.
288    pub fn modules(&self) -> impl Iterator<Item = &str> {
289        self.names()
290            .split(',')
291            .map(str::trim)
292            .filter(|name| !name.is_empty())
293    }
294
295    /// The load order after the first `=`, as written.
296    #[must_use]
297    pub fn order(&self) -> &str {
298        self.0.split_once('=').map_or("", |(_, order)| order)
299    }
300
301    /// Whether the order is `default`, in any case and with surrounding
302    /// spaces: such an entry sets no load order, it takes its modules out of
303    /// the entries before it.
304    #[must_use]
305    pub fn is_default(&self) -> bool {
306        self.order().trim().eq_ignore_ascii_case(DEFAULT_VALUE)
307    }
308
309    /// The text before the first `=`.
310    fn names(&self) -> &str {
311        self.0.split_once('=').map_or("", |(names, _)| names)
312    }
313}
314
315/// The name of an environment variable: not empty, without `=` and without a
316/// NUL. It is kept as written; a process environment matches it
317/// ASCII-case-insensitively.
318#[derive(Clone, Debug, PartialEq, Eq, Hash)]
319pub struct EnvName(String);
320
321impl EnvName {
322    /// `text` as a variable name.
323    ///
324    /// # Errors
325    ///
326    /// [`FieldError::InvalidEnvName`] when `text` is empty or holds a `=`,
327    /// [`FieldError::Nul`] when it holds a NUL.
328    pub fn new(text: impl Into<String>) -> Result<Self, FieldError> {
329        let text = without_nul(text.into())?;
330        if text.is_empty() || text.contains('=') {
331            Err(FieldError::InvalidEnvName(text))
332        } else {
333            Ok(Self(text))
334        }
335    }
336
337    /// The name as written.
338    #[must_use]
339    pub fn as_str(&self) -> &str {
340        &self.0
341    }
342}
343
344/// One environment entry of a rule: a variable and the value it gets, or no
345/// value, which removes the variable.
346#[derive(Clone, Debug, PartialEq, Eq, Hash)]
347pub struct EnvEntry {
348    /// The variable.
349    name: EnvName,
350    /// Its value, not empty, or `None` to remove it.
351    value: Option<String>,
352}
353
354impl EnvEntry {
355    /// The entry that sets `name` to `value`. An empty `value` removes the
356    /// variable, as on the wire, where an empty value is a removal.
357    ///
358    /// # Errors
359    ///
360    /// [`FieldError::Nul`] when `value` holds a NUL.
361    pub fn new(name: EnvName, value: impl Into<String>) -> Result<Self, FieldError> {
362        let value = without_nul(value.into())?;
363        Ok(Self {
364            name,
365            value: (!value.is_empty()).then_some(value),
366        })
367    }
368
369    /// The entry that removes `name`.
370    #[must_use]
371    pub const fn remove(name: EnvName) -> Self {
372        Self { name, value: None }
373    }
374
375    /// The entry that sets `name` to `value`, unchecked: only for the
376    /// literals of this crate, which a test reads back, and for values made
377    /// from checked ones.
378    pub(crate) fn literal(name: &str, value: &str) -> Self {
379        Self {
380            name: EnvName(name.to_owned()),
381            value: (!value.is_empty()).then(|| value.to_owned()),
382        }
383    }
384
385    /// The variable.
386    #[must_use]
387    pub const fn name(&self) -> &EnvName {
388        &self.name
389    }
390
391    /// The value it gets, or `None` when the entry removes it.
392    #[must_use]
393    pub fn value(&self) -> Option<&str> {
394        self.value.as_deref()
395    }
396}
397
398impl fmt::Display for EnvEntry {
399    /// `NAME=value`, or `NAME=` for a removal.
400    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
401        write!(f, "{}={}", self.name, self.value().unwrap_or_default())
402    }
403}
404
405impl FromStr for Fingerprint {
406    type Err = FieldError;
407
408    /// See [`Fingerprint::new`].
409    fn from_str(s: &str) -> Result<Self, Self::Err> {
410        Self::new(s)
411    }
412}
413
414impl FromStr for Argument {
415    type Err = FieldError;
416
417    /// See [`Argument::new`].
418    fn from_str(s: &str) -> Result<Self, Self::Err> {
419        Self::new(s)
420    }
421}
422
423impl FromStr for DebugChannel {
424    type Err = FieldError;
425
426    /// See [`DebugChannel::new`].
427    fn from_str(s: &str) -> Result<Self, Self::Err> {
428        Self::new(s)
429    }
430}
431
432impl FromStr for DllOverride {
433    type Err = FieldError;
434
435    /// See [`DllOverride::new`].
436    fn from_str(s: &str) -> Result<Self, Self::Err> {
437        Self::new(s)
438    }
439}
440
441impl FromStr for EnvName {
442    type Err = FieldError;
443
444    /// See [`EnvName::new`].
445    fn from_str(s: &str) -> Result<Self, Self::Err> {
446        Self::new(s)
447    }
448}
449
450impl fmt::Display for Fingerprint {
451    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
452        f.write_str(self.as_str())
453    }
454}
455
456impl fmt::Display for Argument {
457    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
458        f.write_str(self.as_str())
459    }
460}
461
462impl fmt::Display for DebugChannel {
463    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
464        f.write_str(self.as_str())
465    }
466}
467
468impl fmt::Display for DllOverride {
469    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
470        f.write_str(self.as_str())
471    }
472}
473
474impl fmt::Display for EnvName {
475    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
476        f.write_str(self.as_str())
477    }
478}
479
480impl AsRef<str> for Argument {
481    fn as_ref(&self) -> &str {
482        self.as_str()
483    }
484}
485
486impl PartialEq<&str> for Fingerprint {
487    fn eq(&self, other: &&str) -> bool {
488        self.0 == *other
489    }
490}
491
492impl PartialEq<&str> for Argument {
493    fn eq(&self, other: &&str) -> bool {
494        self.0 == *other
495    }
496}
497
498impl PartialEq<&str> for DebugChannel {
499    fn eq(&self, other: &&str) -> bool {
500        self.0 == *other
501    }
502}
503
504impl PartialEq<&str> for DllOverride {
505    fn eq(&self, other: &&str) -> bool {
506        self.0 == *other
507    }
508}
509
510impl PartialEq<&str> for EnvName {
511    fn eq(&self, other: &&str) -> bool {
512        self.0 == *other
513    }
514}
515
516#[cfg(test)]
517mod tests;