Skip to main content

feature_check/
defs.rs

1// SPDX-FileCopyrightText: Peter Pentchev <roam@ringlet.net>
2// SPDX-License-Identifier: BSD-2-Clause
3//! Common definitions for the feature-check crate's modules.
4
5use std::collections::HashMap;
6use std::error::Error;
7use std::fmt::{Debug, Display, Formatter, Result as FmtResult};
8
9use anyhow::Error as AnyError;
10
11use crate::version::Version;
12
13/// The default option to pass to a program to obtain the list of features.
14pub const DEFAULT_OPTION_NAME: &str = "--features";
15
16/// The default prefix to look for in the lines output by the program.
17pub const DEFAULT_PREFIX: &str = "Features: ";
18
19/// The result of evaluating either a single term or the whole expression.
20#[derive(Debug)]
21#[non_exhaustive]
22pub enum CalcResult {
23    /// No value, e.g. the queried feature is not present.
24    Null,
25    /// A boolean value, usually for the whole expression.
26    Bool(bool),
27    /// A feature's obtained version.
28    Version(Version),
29}
30
31/// An object that may be evaluated and provide a result.
32pub trait Calculable: Debug {
33    /// Get the value of the evaluated term or expression as applied to
34    /// the list of features obtained for the program.
35    ///
36    /// # Errors
37    ///
38    /// Will propagate errors from parsing strings as [`crate::version::Version`] objects.
39    /// Will also return an error if the expression contains comparisons of
40    /// objects of incompatible types (e.g. a version string and a comparison result).
41    fn get_value(&self, features: &HashMap<String, Version>) -> Result<CalcResult, ParseError>;
42}
43
44/// The feature-check operating mode, usually "List".
45#[derive(Debug)]
46#[non_exhaustive]
47pub enum Mode {
48    /// Obtain the list of the program's features.
49    List,
50    /// Obtain the value of a single feature.
51    Single(Box<dyn Calculable + 'static>),
52    /// Evaluate a "feature op version" expression.
53    Simple(Box<dyn Calculable + 'static>),
54}
55
56/// Runtime configuration settings for
57/// the [`obtain_features`][crate::obtain::obtain_features] function.
58#[derive(Debug)]
59#[non_exhaustive]
60pub struct Config {
61    /// The option to pass to the program to query for supported features.
62    pub option_name: String,
63    /// The prefix to look for in the lines output by the program.
64    pub prefix: String,
65    /// The name or full path of the program to execute.
66    pub program: String,
67    /// The feature-check tool's operating mode.
68    pub mode: Mode,
69}
70
71impl Default for Config {
72    #[inline]
73    fn default() -> Self {
74        Self {
75            option_name: DEFAULT_OPTION_NAME.to_owned(),
76            prefix: DEFAULT_PREFIX.to_owned(),
77            program: String::new(),
78            mode: Mode::List,
79        }
80    }
81}
82
83impl Config {
84    /// Replace the option name to query.
85    #[inline]
86    #[must_use]
87    pub fn with_option_name(self, option_name: String) -> Self {
88        Self {
89            option_name,
90            ..self
91        }
92    }
93    /// Replace the prefix to look for in the program output.
94    #[inline]
95    #[must_use]
96    pub fn with_prefix(self, prefix: String) -> Self {
97        Self { prefix, ..self }
98    }
99    /// Replace the name of the program to execute.
100    #[inline]
101    #[must_use]
102    pub fn with_program(self, program: String) -> Self {
103        Self { program, ..self }
104    }
105    /// Replace the query mode.
106    #[inline]
107    #[must_use]
108    pub fn with_mode(self, mode: Mode) -> Self {
109        Self { mode, ..self }
110    }
111}
112
113/// Errors that can occur during parsing a test expression or the features line.
114#[derive(Debug)]
115#[non_exhaustive]
116pub enum ParseError {
117    /// The arguments to a comparison operator are of incompatible types.
118    CannotCompare(String, String),
119
120    /// An unrecognized comparison operator was specified.
121    InvalidComparisonOperator(String),
122
123    /// The arguments to a comparison operator are of unexpected types.
124    Uncomparable(String, String),
125
126    /// A parser failed.
127    ParseFailure(String, AnyError),
128
129    /// A parser left some bytes out.
130    ParseLeftovers(String, usize),
131}
132
133impl Display for ParseError {
134    #[inline]
135    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
136        match *self {
137            Self::CannotCompare(ref left, ref right) => {
138                write!(f, "Cannot compare {left} to {right}")
139            }
140            Self::InvalidComparisonOperator(ref value) => {
141                write!(f, "Invalid comparison operator '{value}'")
142            }
143            Self::Uncomparable(ref left, ref right) => write!(
144                f,
145                "Don't know how to compare {left} to anything, including {right}"
146            ),
147            Self::ParseFailure(ref value, _) => {
148                write!(f, "Could not parse '{value}' as a valid expression")
149            }
150            Self::ParseLeftovers(ref value, ref count) => {
151                write!(
152                    f,
153                    "Could not parse '{value}' as a valid expression: {count} bytes left over"
154                )
155            }
156        }
157    }
158}
159
160impl Error for ParseError {
161    #[inline]
162    fn source(&self) -> Option<&(dyn Error + 'static)> {
163        match *self {
164            Self::CannotCompare(_, _)
165            | Self::InvalidComparisonOperator(_)
166            | Self::Uncomparable(_, _)
167            | Self::ParseLeftovers(_, _) => None,
168            Self::ParseFailure(_, ref err) => Some(err.as_ref()),
169        }
170    }
171}
172
173/// Errors that can occur during querying a program's features.
174#[derive(Debug)]
175#[non_exhaustive]
176pub enum ObtainError {
177    /// The program's output was not a valid UTF-8 string.
178    DecodeOutput(String, AnyError),
179
180    /// The program could not be executed.
181    RunProgram(String, AnyError),
182
183    /// A user-supplied expression could not be parsed.
184    Parse(ParseError),
185}
186
187impl Display for ObtainError {
188    #[inline]
189    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
190        match *self {
191            Self::DecodeOutput(ref prog, _) => write!(
192                f,
193                "Could not decode the {prog} program's output as valid UTF-8"
194            ),
195            Self::RunProgram(ref prog, _) => write!(f, "Could not execute the {prog} program"),
196            Self::Parse(_) => write!(f, "Parse error"),
197        }
198    }
199}
200
201impl Error for ObtainError {
202    #[inline]
203    fn source(&self) -> Option<&(dyn Error + 'static)> {
204        match *self {
205            Self::DecodeOutput(_, ref err) | Self::RunProgram(_, ref err) => Some(err.as_ref()),
206            Self::Parse(ref err) => Some(err),
207        }
208    }
209}
210
211/// The result of querying a program for its supported features.
212#[derive(Debug)]
213#[non_exhaustive]
214pub enum Obtained {
215    /// The program could not be executed at all, or its output could
216    /// not be parsed as a reasonable string.
217    Failed(ObtainError),
218    /// The program does not support being queried for features.
219    NotSupported,
220    /// The program's supported features were successfully parsed.
221    Features(HashMap<String, Version>),
222}