feature_check/obtain.rs
1// SPDX-FileCopyrightText: Peter Pentchev <roam@ringlet.net>
2// SPDX-License-Identifier: BSD-2-Clause
3//! Query a program for its features.
4//!
5//! The [`obtain_features`] function, when passed
6//! a [`Config`] object, will run a program with
7//! the appropriate command-line options, analyze its output, and
8//! build up a list of the supported features.
9//!
10//! See the crate-level documentation for sample usage.
11
12use std::process::Command;
13
14use anyhow::Context as _;
15
16use crate::defs::{Config, ObtainError, Obtained};
17use crate::expr::parser;
18
19/// Decode a program's output, assuming it is a valid UTF-8 string.
20fn decode_output(program: &str, output: Vec<u8>) -> Result<String, ObtainError> {
21 // FIXME: handle non-UTF-8 stuff
22 String::from_utf8(output)
23 .context("Could not decode a valid UTF-8 string")
24 .map_err(|err| ObtainError::DecodeOutput(program.to_owned(), err))
25}
26
27/// Run the specified program, analyze its output.
28///
29/// See the crate-level documentation for sample usage.
30///
31/// # Errors
32///
33/// Will return [`Obtained::Failed`] if the program cannot be executed.
34/// Will propagate errors from decoding the program's output as UTF-8 lines.
35#[inline]
36#[expect(clippy::module_name_repetitions, reason = "sensible name")]
37pub fn obtain_features(config: &Config) -> Result<Obtained, ObtainError> {
38 match Command::new(&config.program)
39 .args([&config.option_name])
40 .output()
41 .context(format!("Could not execute {}", config.program))
42 {
43 Ok(output) => {
44 if output.status.success() {
45 decode_output(&config.program, output.stdout)?
46 .lines()
47 .find_map(|line| line.strip_prefix(&config.prefix))
48 .map_or(Ok(Obtained::NotSupported), |line| {
49 Ok(Obtained::Features(
50 parser::parse_features_line(line).map_err(ObtainError::Parse)?,
51 ))
52 })
53 } else {
54 Ok(Obtained::NotSupported)
55 }
56 }
57 Err(err) => Ok(Obtained::Failed(ObtainError::RunProgram(
58 config.program.clone(),
59 err,
60 ))),
61 }
62}