1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
// Copyright (C) 2023 Andreas Hartmann <hartan@7x.de>
// GNU General Public License v3.0+ (https://www.gnu.org/licenses/gpl-3.0.txt)
// SPDX-License-Identifier: GPL-3.0-or-later
//! Helper for matching [command output](std::process::Output).
use std::borrow::Cow;
/// Match substrings in [command output](std::process::Output).
///
/// Due to the problems described in [`crate::env::ExecutionError::NonZero`], the output of
/// [`crate::env::Environment::output_of()`] doesn't reliably catch the contents of *stderr*
/// in the actual *stderr* variable. For [`Provider`s][crate::provider::Provider], this means
/// that when searching for error strings in command output, **both stdout and stderr** must be
/// checked for occurences of the pattern. This struct removes a lof of the resulting boilerplate
/// code for first converting a `Vec<u8>` into something string-like, and then matching *stdout*
/// and *stderr* against a pattern.
pub struct OutputMatcher<'a> {
stdout: Cow<'a, str>,
stderr: Cow<'a, str>,
status: std::process::ExitStatus,
}
impl<'a> OutputMatcher<'a> {
/// Create a new instance of [`OutputMatcher`] for the given command output.
pub fn new(output: &'a std::process::Output) -> Self {
Self {
stdout: String::from_utf8_lossy(&output.stdout),
stderr: String::from_utf8_lossy(&output.stderr),
status: output.status,
}
}
/// Access stdout with all leading/trailing whitespaces trimmed.
fn stdout(&self) -> &str {
self.stdout.trim()
}
/// Access stderr with all leading/trailing whitespaces trimmed.
fn stderr(&self) -> &str {
self.stderr.trim()
}
/// Returns true if either stdout or stderr starts with `pat`.
pub fn starts_with(&self, pat: &str) -> bool {
self.stdout().starts_with(pat) || self.stderr().starts_with(pat)
}
/// Returns true if either stdout or stderr contains `pat`.
pub fn contains(&self, pat: &str) -> bool {
self.stdout().contains(pat) || self.stderr().contains(pat)
}
/// Returns true if either stdout or stderr ends with `pat`.
pub fn ends_with(&self, pat: &str) -> bool {
self.stdout().ends_with(pat) || self.stderr().ends_with(pat)
}
/// Returns the raw error code of the command output.
pub fn exit_code(&self) -> Option<i32> {
self.status.code()
}
/// Returns true if **both** stdout **and** stderr are empty (except for whitespace)
pub fn is_empty(&self) -> bool {
self.stdout().is_empty() && self.stderr().is_empty()
}
}
impl<'x> From<&'x std::process::Output> for OutputMatcher<'x> {
fn from(value: &'x std::process::Output) -> Self {
Self::new(value)
}
}