//! Common utilities for analyzing build logs across different environments.
//!
//! This module provides common functionality for analyzing build logs from various
//! environments and identifying problems. It contains a wide range of pattern matchers
//! and problem extraction logic that can be used by different log analyzers.
//!
//! NOTE: This module is currently a direct port from Python and needs refactoring.

use crate::lines::Lines;
use crate::problems::common::*;
use crate::r#match::{Error, Matcher, MatcherGroup, RegexLineMatcher};
use crate::regex_line_matcher;
use crate::regex_para_matcher;
use crate::{Match, Problem};
use crate::{MultiLineMatch, Origin, SingleLineMatch};
use lazy_regex::{regex_captures, regex_is_match};
use regex::Captures;

/// Type alias for the common return type of build failure functions
pub type BuildFailureResult = (Option<Box<dyn Match>>, Option<Box<dyn Problem>>);

/// Type alias for matcher error result with match and problem
pub type MatcherResult = Result<Option<(Box<dyn Match>, Option<Box<dyn Problem>>)>, Error>;

fn node_module_missing(c: &Captures) -> Result<Option<Box<dyn Problem>>, Error> {
    if c.get(1).unwrap().as_str().starts_with("/<<PKGBUILDDIR>>/") {
        return Ok(None);
    }
    if c.get(1).unwrap().as_str().starts_with("./") {
        return Ok(None);
    }
    Ok(Some(Box::new(MissingNodeModule(
        c.get(1).unwrap().as_str().to_string(),
    ))))
}

fn file_not_found(c: &Captures) -> Result<Option<Box<dyn Problem>>, Error> {
    let path = c.get(1).unwrap().as_str();
    if path.starts_with('/') && !path.starts_with("/<<PKGBUILDDIR>>") {
        return Ok(Some(Box::new(MissingFile {
            path: std::path::PathBuf::from(path),
        })));
    }
    if let Some(filename) = path.strip_prefix("/<<PKGBUILDDIR>>/") {
        return Ok(Some(Box::new(MissingBuildFile {
            filename: filename.to_string(),
        })));
    }
    if path == ".git/HEAD" {
        return Ok(Some(Box::new(VcsControlDirectoryNeeded {
            vcs: vec!["git".to_string()],
        })));
    }
    if path == "CVS/Root" {
        return Ok(Some(Box::new(VcsControlDirectoryNeeded {
            vcs: vec!["cvs".to_string()],
        })));
    }
    if !path.contains('/') {
        // Maybe a missing command?
        return Ok(Some(Box::new(MissingBuildFile {
            filename: path.to_string(),
        })));
    }
    Ok(None)
}

fn file_not_found_maybe_executable(p: &str) -> Result<Option<Box<dyn Problem>>, Error> {
    if p.starts_with('/') && !p.starts_with("/<<PKGBUILDDIR>>") {
        return Ok(Some(Box::new(MissingFile {
            path: std::path::PathBuf::from(p),
        })));
    }

    if !p.contains('/') {
        // Maybe a missing command?
        return Ok(Some(Box::new(MissingCommandOrBuildFile {
            filename: p.to_string(),
        })));
    }
    Ok(None)
}

fn interpreter_missing(c: &Captures) -> Result<Option<Box<dyn Problem>>, Error> {
    if c.get(1).unwrap().as_str().starts_with('/') {
        if c.get(1).unwrap().as_str().contains("PKGBUILDDIR") {
            return Ok(None);
        }
        return Ok(Some(Box::new(MissingFile {
            path: std::path::PathBuf::from(c.get(1).unwrap().as_str().to_string()),
        })));
    }
    if c.get(1).unwrap().as_str().contains('/') {
        return Ok(None);
    }
    Ok(Some(Box::new(MissingCommand(
        c.get(1).unwrap().as_str().to_string(),
    ))))
}

fn pkg_config_missing(c: &Captures) -> Result<Option<Box<dyn Problem>>, Error> {
    let expr = c.get(1).unwrap().as_str().split('\t').next().unwrap();
    if let Some((pkg, minimum)) = expr.split_once(">=") {
        return Ok(Some(Box::new(MissingPkgConfig {
            module: pkg.trim().to_string(),
            minimum_version: Some(minimum.trim().to_string()),
        })));
    }
    if !expr.contains(' ') {
        return Ok(Some(Box::new(MissingPkgConfig {
            module: expr.to_string(),
            minimum_version: None,
        })));
    }
    // Hmmm
    Ok(None)
}

fn command_missing(c: &Captures) -> Result<Option<Box<dyn Problem>>, Error> {
    let command = c.get(1).unwrap().as_str();
    if command.contains("PKGBUILDDIR") {
        return Ok(None);
    }
    if command == "./configure" {
        return Ok(Some(Box::new(MissingConfigure)));
    }
    if command.starts_with("./") || command.starts_with("../") {
        return Ok(None);
    }
    if command == "debian/rules" {
        return Ok(None);
    }
    Ok(Some(Box::new(MissingCommand(command.to_string()))))
}

lazy_static::lazy_static! {
    static ref CONFIGURE_LINE_MATCHERS: MatcherGroup = MatcherGroup::new(vec![
        regex_line_matcher!(
            r"^\s*Unable to find (.*) \(http(.*)\)",
            |m| Ok(Some(Box::new(MissingVagueDependency{
                name: m.get(1).unwrap().as_str().to_string(),
                url: Some(m.get(2).unwrap().as_str().to_string()),
                minimum_version: None,
                current_version: None,
            })))
        ),
        regex_line_matcher!(
            r"^\s*Unable to find (.*)\.",
            |m| Ok(Some(Box::new(MissingVagueDependency{
                name: m.get(1).unwrap().as_str().to_string(),
                url: None,
                minimum_version: None,
                current_version: None,
            })))
        ),
    ]);
}

#[derive(Debug, Clone)]
struct MultiLineConfigureErrorMatcher;

impl Matcher for MultiLineConfigureErrorMatcher {
    fn extract_from_lines(
        &self,
        lines: &[&str],
        offset: usize,
    ) -> Result<Option<(Box<dyn Match>, Option<Box<dyn Problem>>)>, Error> {
        if lines[offset].trim_end_matches(['\r', '\n']) != "configure: error:" {
            return Ok(None);
        }

        let mut relevant_linenos = vec![];
        for (j, line) in lines.enumerate_forward(None).skip(offset + 1) {
            if line.trim().is_empty() {
                continue;
            }
            relevant_linenos.push(j);
            let m = CONFIGURE_LINE_MATCHERS.extract_from_lines(lines, j)?;
            if let Some(m) = m {
                return Ok(Some(m));
            }
        }

        let m = MultiLineMatch::new(
            Origin("configure".into()),
            relevant_linenos.clone(),
            lines
                .iter()
                .enumerate()
                .filter(|(i, _)| relevant_linenos.contains(i))
                .map(|(_, l)| l.to_string())
                .collect(),
        );

        Ok(Some((Box::new(m), None)))
    }
}

#[derive(Debug, Clone)]
struct HaskellMissingDependencyMatcher;

impl Matcher for HaskellMissingDependencyMatcher {
    fn extract_from_lines(
        &self,
        lines: &[&str],
        offset: usize,
    ) -> Result<Option<(Box<dyn Match>, Option<Box<dyn Problem>>)>, Error> {
        if !regex_is_match!(
            r"(.*): Encountered missing or private dependencies:",
            lines[offset].trim_end_matches('\n')
        ) {
            return Ok(None);
        }

        let mut deps = vec![];
        let mut offsets = vec![offset];

        for (offset, line) in lines.enumerate_forward(None).skip(offset + 1) {
            if line.trim().is_empty() {
                break;
            }
            if let Some((dep, _)) = line.trim().split_once(',') {
                deps.push(dep.to_string());
            }
            offsets.push(offset);
        }
        let m = MultiLineMatch {
            origin: Origin("haskell dependencies".into()),
            offsets: offsets.clone(),
            lines: offsets.iter().map(|i| lines[*i].to_string()).collect(),
        };
        let p = MissingHaskellDependencies(deps);
        Ok(Some((Box::new(m), Some(Box::new(p)))))
    }
}

#[derive(Debug, Clone)]
struct SetupPyCommandMissingMatcher;

impl Matcher for SetupPyCommandMissingMatcher {
    fn extract_from_lines(
        &self,
        lines: &[&str],
        offset: usize,
    ) -> Result<Option<(Box<dyn Match>, Option<Box<dyn Problem>>)>, Error> {
        let first_offset = offset;
        let command =
            match regex_captures!(r"error: invalid command \'(.*)\'", lines[offset].trim()) {
                None => return Ok(None),
                Some((_, command)) => command,
            };

        for j in 0..20 {
            let offset = offset - j;
            let line = lines[offset].trim_end_matches('\n');

            if regex_is_match!(
                r"usage: setup.py \[global_opts\] cmd1 \[cmd1_opts\] \[cmd2 \[cmd2_opts\] \.\.\.\]",
                line,
            ) {
                let offsets: Vec<usize> = vec![first_offset];
                let m = MultiLineMatch {
                    origin: Origin("setup.py".into()),
                    offsets,
                    lines: vec![lines[first_offset].to_string()],
                };

                let p = MissingSetupPyCommand(command.to_string());
                return Ok(Some((Box::new(m), Some(Box::new(p)))));
            }
        }

        log::warn!("Unable to find setup.py usage line");
        Ok(None)
    }
}

#[derive(Debug, Clone)]
struct PythonFileNotFoundErrorMatcher;

impl Matcher for PythonFileNotFoundErrorMatcher {
    fn extract_from_lines(
        &self,
        lines: &[&str],
        offset: usize,
    ) -> Result<Option<(Box<dyn Match>, Option<Box<dyn Problem>>)>, Error> {
        if let Some((_, name)) = lazy_regex::regex_captures!(
            r"^(?:E  +)?FileNotFoundError: \[Errno 2\] No such file or directory: \'(.*)\'",
            lines[offset].trim_end_matches('\n')
        ) {
            if offset > 2 && lines[offset - 2].contains("subprocess") {
                return Ok(Some((
                    Box::new(SingleLineMatch {
                        origin: Origin("python".into()),
                        offset,
                        line: lines[offset].to_string(),
                    }),
                    Some(Box::new(MissingCommand(name.to_string()))),
                )));
            } else {
                return Ok(Some((
                    Box::new(SingleLineMatch {
                        origin: Origin("python".into()),
                        offset,
                        line: lines[offset].to_string(),
                    }),
                    file_not_found_maybe_executable(name)?,
                )));
            }
        }

        Ok(None)
    }
}

#[derive(Debug, Clone)]
struct MultiLinePerlMissingModulesErrorMatcher;

impl Matcher for MultiLinePerlMissingModulesErrorMatcher {
    fn extract_from_lines(
        &self,
        lines: &[&str],
        offset: usize,
    ) -> Result<Option<(Box<dyn Match>, Option<Box<dyn Problem>>)>, Error> {
        let line = lines[offset].trim_end_matches(['\r', '\n']);
        if line != "# The following modules are not available." {
            return Ok(None);
        }
        if lines[offset + 1].trim_end_matches(['\r', '\n'])
            != "# `perl Makefile.PL | cpanm` will install them:"
        {
            return Ok(None);
        }

        let relevant_linenos = vec![offset, offset + 1, offset + 2];

        let m = MultiLineMatch::new(
            Origin("perl line match".into()),
            relevant_linenos.clone(),
            lines
                .iter()
                .enumerate()
                .filter(|(i, _)| relevant_linenos.contains(i))
                .map(|(_, l)| l.to_string())
                .collect(),
        );

        let problem: Option<Box<dyn Problem>> = Some(Box::new(MissingPerlModule::simple(
            lines[offset + 2].trim(),
        )));

        Ok(Some((Box::new(m), problem)))
    }
}

lazy_static::lazy_static! {
    static ref VIGNETTE_LINE_MATCHERS: MatcherGroup = MatcherGroup::new(vec![
        regex_line_matcher!(r"^([^ ]+) is not available", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))),
        regex_line_matcher!(r"^The package `(.*)` is required\.", |m| Ok(Some(Box::new(MissingRPackage::simple(m.get(1).unwrap().as_str()))))),
        regex_line_matcher!(r"^Package '(.*)' required.*", |m| Ok(Some(Box::new(MissingRPackage::simple(m.get(1).unwrap().as_str()))))),
        regex_line_matcher!(r"^The '(.*)' package must be installed.*", |m| Ok(Some(Box::new(MissingRPackage::simple(m.get(1).unwrap().as_str()))))),
    ]);
}

#[derive(Debug, Clone)]
struct MultiLineVignetteErrorMatcher;

impl Matcher for MultiLineVignetteErrorMatcher {
    fn extract_from_lines(
        &self,
        lines: &[&str],
        offset: usize,
    ) -> Result<Option<(Box<dyn Match>, Option<Box<dyn Problem>>)>, Error> {
        let header_m =
            regex::Regex::new(r"^Error: processing vignette '(.*)' failed with diagnostics:")
                .unwrap();

        if !header_m.is_match(lines[offset]) {
            return Ok(None);
        }

        if let Some((m, p)) = VIGNETTE_LINE_MATCHERS.extract_from_lines(lines, offset + 1)? {
            return Ok(Some((m, p)));
        }

        Ok(Some((
            Box::new(SingleLineMatch {
                origin: Origin("vignette line match".into()),
                offset: offset + 1,
                line: lines[offset + 1].to_string(),
            }),
            None,
        )))
    }
}

#[derive(Debug, Clone)]
struct AutoconfUnexpectedMacroMatcher;

impl Matcher for AutoconfUnexpectedMacroMatcher {
    fn extract_from_lines(
        &self,
        lines: &[&str],
        offset: usize,
    ) -> Result<Option<(Box<dyn Match>, Option<Box<dyn Problem>>)>, Error> {
        if !regex_is_match!(
            r"\./configure: line [0-9]+: syntax error near unexpected token `.+'",
            lines[offset]
        ) {
            return Ok(None);
        }

        let m = MultiLineMatch::new(
            Origin("autoconf unexpected macro".into()),
            vec![offset, offset + 1],
            vec![lines[offset].to_string(), lines[offset + 1].to_string()],
        );

        let problem = if let Some((_, r#macro)) = regex_captures!(
            r"^\./configure: line [0-9]+: `[\s\t]*([A-Z0-9_]+)\(.*",
            lines[offset + 1]
        ) {
            Some(Box::new(MissingAutoconfMacro {
                r#macro: r#macro.to_string(),
                need_rebuild: true,
            }) as Box<dyn Problem>)
        } else {
            None
        };

        Ok(Some((Box::new(m), problem)))
    }
}

fn maven_missing_artifact(m: &regex::Captures) -> Result<Option<Box<dyn Problem>>, Error> {
    let artifacts = m
        .get(1)
        .unwrap()
        .as_str()
        .split(',')
        .map(|s| s.trim().to_string())
        .collect::<Vec<_>>();
    Ok(Some(Box::new(MissingMavenArtifacts(artifacts))))
}

fn r_missing_package(m: &regex::Captures) -> Result<Option<Box<dyn Problem>>, Error> {
    let fragment = m.get(1).unwrap().as_str();
    let deps = fragment
        .split(",")
        .map(|dep| {
            dep.trim_matches('‘')
                .trim_matches('’')
                .trim_matches('\'')
                .to_string()
        })
        .collect::<Vec<_>>();
    Ok(Some(Box::new(MissingRPackage::simple(&deps[0]))))
}

fn webpack_file_missing(m: &regex::Captures) -> Result<Option<Box<dyn Problem>>, Error> {
    let path = std::path::Path::new(m.get(1).unwrap().as_str());
    let container = std::path::Path::new(m.get(2).unwrap().as_str());
    let path = container.join(path);
    if path.starts_with("/") && !path.as_path().starts_with("/<<PKGBUILDDIR>>") {
        return Ok(Some(Box::new(MissingFile { path })));
    }
    Ok(None)
}

fn ruby_missing_gem(m: &regex::Captures) -> Result<Option<Box<dyn Problem>>, Error> {
    let mut minimum_version = None;
    for grp in m.get(2).unwrap().as_str().split(",") {
        if let Some((cond, val)) = grp.trim().split_once(" ") {
            if cond == ">=" {
                minimum_version = Some(val.to_string());
                break;
            }
            if cond == "~>" {
                minimum_version = Some(val.to_string());
            }
        }
    }
    Ok(Some(Box::new(MissingRubyGem::new(
        m.get(1).unwrap().as_str().to_string(),
        minimum_version,
    ))))
}

const MAVEN_ERROR_PREFIX: &str = "(?:\\[ERROR\\]|\\[\x1b\\[1;31mERROR\x1b\\[m\\]) ";

lazy_static::lazy_static! {
    static ref COMMON_MATCHERS: MatcherGroup = MatcherGroup::new(vec![
        regex_line_matcher!(r"^[^:]+:\d+: (.*): No such file or directory$", |m| file_not_found_maybe_executable(m.get(1).unwrap().as_str())),
        regex_line_matcher!(
        r"^(distutils.errors.DistutilsError|error): Could not find suitable distribution for Requirement.parse\('([^']+)'\)$",
        |c| {
            let req = c.get(2).unwrap().as_str().split(';').next().unwrap();
            Ok(Some(Box::new(MissingPythonDistribution::from_requirement_str(req, None))))
        }),
        regex_line_matcher!(
            r"^We need the Python library (.*) to be installed. Try runnning: python -m ensurepip$",
            |c| Ok(Some(Box::new(MissingPythonDistribution { distribution: c.get(1).unwrap().as_str().to_string(), python_version: None, minimum_version: None })))),
        regex_line_matcher!(
            r"^pkg_resources.DistributionNotFound: The '([^']+)' distribution was not found and is required by the application$",
            |c| Ok(Some(Box::new(MissingPythonDistribution::from_requirement_str(c.get(1).unwrap().as_str(), None))))),
        regex_line_matcher!(
            r"^pkg_resources.DistributionNotFound: The '([^']+)' distribution was not found and is required by (.*)$",
            |c| Ok(Some(Box::new(MissingPythonDistribution::from_requirement_str(c.get(1).unwrap().as_str(), None))))),
        regex_line_matcher!(
            r"^Please install cmake version >= (.*) and re-run setup$",
            |_| Ok(Some(Box::new(MissingCommand("cmake".to_string()))))),
        regex_line_matcher!(
            r"^pluggy.manager.PluginValidationError: Plugin '.*' could not be loaded: \(.* \(/usr/lib/python2.[0-9]/dist-packages\), Requirement.parse\('(.*)'\)\)!$",
            |c| {
                let expr = c.get(1).unwrap().as_str();
                let python_version = Some(2);
                if let Some((pkg, minimum)) = expr.split_once(">=") {
                    Ok(Some(Box::new(MissingPythonModule {
                        module: pkg.trim().to_string(),
                        python_version,
                        minimum_version: Some(minimum.trim().to_string()),
                    })))
                } else if !expr.contains(' ') {
                    Ok(Some(Box::new(MissingPythonModule {
                        module: expr.trim().to_string(),
                        python_version,
                        minimum_version: None,
                    })))
                }
                else {
                    Ok(None)
                }
            }),
        regex_line_matcher!(r"^E ImportError: (.*) could not be imported\.$", |m| Ok(Some(Box::new(MissingPythonModule {
            module: m.get(1).unwrap().as_str().to_string(),
            python_version: None,
            minimum_version: None
        })))),
        regex_line_matcher!(r"^ImportError: could not find any library for ([^ ]+) .*$", |m| Ok(Some(Box::new(MissingLibrary(m.get(1).unwrap().as_str().to_string()))))),
        regex_line_matcher!(r"^ImportError: cannot import name (.*), introspection typelib not found$", |m| Ok(Some(Box::new(MissingIntrospectionTypelib(m.get(1).unwrap().as_str().to_string()))))),
        regex_line_matcher!(r"^ValueError: Namespace (.*) not available$", |m| Ok(Some(Box::new(MissingIntrospectionTypelib(m.get(1).unwrap().as_str().to_string()))))),
        regex_line_matcher!(r"^  namespace '(.*)' ([^ ]+) is being loaded, but >= ([^ ]+) is required$", |m| {
            let package = m.get(1).unwrap().as_str();
            let min_version = m.get(3).unwrap().as_str();

            Ok(Some(Box::new(MissingRPackage {
                package: package.to_string(),
                minimum_version: Some(min_version.to_string()),
            })))
        }),
        regex_line_matcher!("^ImportError: cannot import name '(.*)' from '(.*)'$", |m| {
            let module = m.get(2).unwrap().as_str();
            let name = m.get(1).unwrap().as_str();
            // TODO(jelmer): This name won't always refer to a module
            let name = format!("{}.{}", module, name);
            Ok(Some(Box::new(MissingPythonModule {
                module: name,
                python_version: None,
                minimum_version: None,
            })))
        }),
        regex_line_matcher!("^E       fixture '(.*)' not found$", |m| Ok(Some(Box::new(MissingPytestFixture(m.get(1).unwrap().as_str().to_string()))))),
        regex_line_matcher!("^pytest: error: unrecognized arguments: (.*)$", |m| {
            let args = shlex::split(m.get(1).unwrap().as_str()).unwrap();
            Ok(Some(Box::new(UnsupportedPytestArguments(args))))
        }),
        regex_line_matcher!(
            "^INTERNALERROR> pytest.PytestConfigWarning: Unknown config option: (.*)$",
            |m| Ok(Some(Box::new(UnsupportedPytestConfigOption(m.get(1).unwrap().as_str().to_string()))))),
        regex_line_matcher!("^E   ImportError: cannot import name '(.*)' from '(.*)'", |m| {
            let name = m.get(1).unwrap().as_str();
            let module = m.get(2).unwrap().as_str();
            Ok(Some(Box::new(MissingPythonModule {
                module: format!("{}.{}", module, name),
                python_version: None,
                minimum_version: None,
            })))
        }),
        regex_line_matcher!("^E   ImportError: cannot import name ([^']+)", |m| {
            Ok(Some(Box::new(MissingPythonModule {
                module: m.get(1).unwrap().as_str().to_string(),
                python_version: None,
                minimum_version: None,
            })))
        }),
        regex_line_matcher!(r"^django.core.exceptions.ImproperlyConfigured: Error loading .* module: No module named '(.*)'", |m| {
            Ok(Some(Box::new(MissingPythonModule {
                module: m.get(1).unwrap().as_str().to_string(),
                python_version: None,
                minimum_version: None,
            })))
        }),
        regex_line_matcher!("^E   ImportError: No module named (.*)", |m| {
            Ok(Some(Box::new(MissingPythonModule {
                module: m.get(1).unwrap().as_str().to_string(),
                python_version: None,
                minimum_version: None,
            })))
        }),
        regex_line_matcher!(r"^\s*ModuleNotFoundError: No module named '(.*)'",|m| {
            Ok(Some(Box::new(MissingPythonModule {
                module: m.get(1).unwrap().as_str().to_string(),
                python_version: Some(3),
                minimum_version: None,
            })))
        }),
        regex_line_matcher!(r"^Could not import extension .* \(exception: No module named (.*)\)", |m| {
            Ok(Some(Box::new(MissingPythonModule {
                module: m.get(1).unwrap().as_str().trim().to_string(),
                python_version: None,
                minimum_version: None,
            })))
        }),
        regex_line_matcher!(r"^Could not import (.*)\.", |m| {
            Ok(Some(Box::new(MissingPythonModule {
                module: m.get(1).unwrap().as_str().trim().to_string(),
                python_version: None,
                minimum_version: None,
            })))
        }),
        regex_line_matcher!(r"^(.*): Error while finding module specification for '(.*)' \(ModuleNotFoundError: No module named '(.*)'\)", |m| {
            let exec = m.get(1).unwrap().as_str();
            let python_version = if exec.ends_with("python3") {
                Some(3)
            } else if exec.ends_with("python2") {
                Some(2)
            } else {
                None
            };

            Ok(Some(Box::new(MissingPythonModule {
                module: m.get(3).unwrap().as_str().trim().to_string(),
                python_version,
                minimum_version: None,
            })))}),
        regex_line_matcher!("^E   ModuleNotFoundError: No module named '(.*)'", |m| {
            Ok(Some(Box::new(MissingPythonModule {
                module: m.get(1).unwrap().as_str().to_string(),
                python_version: Some(3),
                minimum_version: None
            })))
        }),
        regex_line_matcher!(r"^/usr/bin/python3: No module named ([^ ]+).*", |m| {
            Ok(Some(Box::new(MissingPythonModule {
                module: m.get(1).unwrap().as_str().to_string(),
                python_version: Some(3),
                minimum_version: None,
            })))
        }),
        regex_line_matcher!(r#"^(.*:[0-9]+|package .*): cannot find package "(.*)" in any of:"#, |m| Ok(Some(Box::new(MissingGoPackage { package: m.get(2).unwrap().as_str().to_string() })))),
        regex_line_matcher!(r#"^ImportError: Error importing plugin ".*": No module named (.*)"#, |m| {
            Ok(Some(Box::new(MissingPythonModule {
                module: m.get(1).unwrap().as_str().to_string(),
                python_version: None,
                minimum_version: None,
            })))
        }),
        regex_line_matcher!(r"^ImportError: No module named (.*)", |m| {
            Ok(Some(Box::new(MissingPythonModule {
                module: m.get(1).unwrap().as_str().to_string(),
                python_version: None,
                minimum_version: None,
            })))
        }),
        regex_line_matcher!(r"^[^:]+:\d+:\d+: fatal error: (.+\.h|.+\.hh|.+\.hpp): No such file or directory", |m| Ok(Some(Box::new(MissingCHeader { header: m.get(1).unwrap().as_str().to_string() })))),
        regex_line_matcher!(r"^[^:]+:\d+:\d+: fatal error: (.+\.xpm): No such file or directory", file_not_found),
        regex_line_matcher!(r".*fatal: not a git repository \(or any parent up to mount point /\)", |_| Ok(Some(Box::new(VcsControlDirectoryNeeded { vcs: vec!["git".to_string()] })))),
        regex_line_matcher!(r".*fatal: not a git repository \(or any of the parent directories\): \.git", |_| Ok(Some(Box::new(VcsControlDirectoryNeeded { vcs: vec!["git".to_string()] })))),
        regex_line_matcher!(r"[^:]+\.[ch]:\d+:\d+: fatal error: (.+): No such file or directory", |m| Ok(Some(Box::new(MissingCHeader { header: m.get(1).unwrap().as_str().to_string() })))),
        regex_line_matcher!("^.*␛\x1b\\[31mERROR:␛\x1b\\[39m Error: Cannot find module '(.*)'", node_module_missing),
    regex_line_matcher!("^\x1b\\[2mError: Cannot find module '(.*)'", node_module_missing),
    regex_line_matcher!("^\x1b\\[1m\x1b\\[31m\\[!\\] \x1b\\[1mError: Cannot find module '(.*)'", node_module_missing),
    regex_line_matcher!("^✖ \x1b\\[31mERROR:\x1b\\[39m Error: Cannot find module '(.*)'", node_module_missing),
    regex_line_matcher!("^\x1b\\[0;31m  Error: To use the transpile option, you must have the '(.*)' module installed",
     node_module_missing),
    regex_line_matcher!(r#"^\[31mError: No test files found: "(.*)"\[39m"#),
    regex_line_matcher!(r#"^\x1b\[31mError: No test files found: "(.*)"\x1b\[39m"#),
    regex_line_matcher!(r"^\s*Error: Cannot find module '(.*)'", node_module_missing),
    regex_line_matcher!(r"^>> Error: Cannot find module '(.*)'", node_module_missing),
    regex_line_matcher!(r"^>> Error: Cannot find module '(.*)' from '.*'", node_module_missing),
    regex_line_matcher!(r"^Error: Failed to load parser '.*' declared in '.*': Cannot find module '(.*)'", |m| Ok(Some(Box::new(MissingNodeModule(m.get(1).unwrap().as_str().to_string()))))),
    regex_line_matcher!(r"^    Cannot find module '(.*)' from '.*'", |m| Ok(Some(Box::new(MissingNodeModule(m.get(1).unwrap().as_str().to_string()))))),
    regex_line_matcher!(r"^>> Error: Grunt attempted to load a \.coffee file but CoffeeScript was not installed\.", |_| Ok(Some(Box::new(MissingNodePackage("coffeescript".to_string()))))),
    regex_line_matcher!(r"^>> Got an unexpected exception from the coffee-script compiler. The original exception was: Error: Cannot find module '(.*)'", node_module_missing),
    regex_line_matcher!(r"^\s*Module not found: Error: Can't resolve '(.*)' in '(.*)'", node_module_missing),
    regex_line_matcher!(r"^  Module (.*) in the transform option was not found\.", node_module_missing),
    regex_line_matcher!(
        r"^libtool/glibtool not found!",
        |_| Ok(Some(Box::new(MissingVagueDependency::simple("libtool"))))),
    regex_line_matcher!(r"^qmake: could not find a Qt installation of ''", |_| Ok(Some(Box::new(MissingQt)))),
    regex_line_matcher!(r"^Cannot find X include files via .*", |_| Ok(Some(Box::new(MissingX11)))),
    regex_line_matcher!(
        r"^\*\*\* No X11! Install X-Windows development headers/libraries! \*\*\*",
        |_| Ok(Some(Box::new(MissingX11)))
    ),
    regex_line_matcher!(
        r"^configure: error: \*\*\* No X11! Install X-Windows development headers/libraries! \*\*\*",
        |_| Ok(Some(Box::new(MissingX11)))
    ),
    regex_line_matcher!(
        r"^configure: error: The Java compiler javac failed.*",
        |_| Ok(Some(Box::new(MissingCommand("javac".to_string()))))
    ),
    regex_line_matcher!(
        r"^configure: error: No ([^ ]+) command found",
        |m| Ok(Some(Box::new(MissingCommand(m.get(1).unwrap().as_str().to_string()))))
    ),
    regex_line_matcher!(
        r"^ERROR: InvocationError for command could not find executable (.*)",
        |m| Ok(Some(Box::new(MissingCommand(m.get(1).unwrap().as_str().to_string()))))
    ),
    regex_line_matcher!(
        r"^  \*\*\* The (.*) script could not be found\. .*",
        |m| Ok(Some(Box::new(MissingCommand(m.get(1).unwrap().as_str().to_string()))))
    ),
    regex_line_matcher!(
        r#"^(.*)" command could not be found. (.*)"#,
        |m| Ok(Some(Box::new(MissingCommand(m.get(1).unwrap().as_str().to_string()))))
    ),
    regex_line_matcher!(
        r"^configure: error: cannot find lib ([^ ]+)",
        |m| Ok(Some(Box::new(MissingLibrary(m.get(1).unwrap().as_str().to_string()))))
    ),
    regex_line_matcher!(r#"^>> Local Npm module "(.*)" not found. Is it installed?"#, node_module_missing),
    regex_line_matcher!(
        r"^npm ERR! CLI for webpack must be installed.",
        |_| Ok(Some(Box::new(MissingNodePackage("webpack-cli".to_string()))))
    ),
    regex_line_matcher!(r"^npm ERR! \[!\] Error: Cannot find module '(.*)'", node_module_missing),
    regex_line_matcher!(
        r#"^npm ERR! >> Local Npm module "(.*)" not found. Is it installed\?"#,
        node_module_missing
    ),
    regex_line_matcher!(r"^npm ERR! Error: Cannot find module '(.*)'", node_module_missing),
    regex_line_matcher!(
        r"^npm ERR! ERROR in Entry module not found: Error: Can't resolve '(.*)' in '.*'",
        node_module_missing
    ),
    regex_line_matcher!(r"^npm ERR! sh: [0-9]+: (.*): not found", command_missing),
    regex_line_matcher!(r"^npm ERR! (.*\.ts)\([0-9]+,[0-9]+\): error TS[0-9]+: Cannot find module '(.*)' or its corresponding type declarations.", |m| Ok(Some(Box::new(MissingNodeModule(m.get(2).unwrap().as_str().to_string()))))),
    regex_line_matcher!(r"^npm ERR! Error: spawn (.*) ENOENT", command_missing),

    regex_line_matcher!(
        r"^(\./configure): line \d+: ([A-Z0-9_]+): command not found",
        |m| Ok(Some(Box::new(MissingAutoconfMacro::new(m.get(2).unwrap().as_str().to_string()))))
    ),
    regex_line_matcher!(r"^.*: line \d+: ([^ ]+): command not found", command_missing),
    regex_line_matcher!(r"^.*: line \d+: ([^ ]+): Permission denied"),
    regex_line_matcher!(r"^make\[[0-9]+\]: .*: Permission denied"),
    regex_line_matcher!(r"^/usr/bin/texi2dvi: TeX neither supports -recorder nor outputs \\openout lines in its log file"),
    regex_line_matcher!(r"^/bin/sh: \d+: ([^ ]+): not found", command_missing),
    regex_line_matcher!(r"^sh: \d+: ([^ ]+): not found", command_missing),
    regex_line_matcher!(r"^.*\.sh: \d+: ([^ ]+): not found", command_missing),
    regex_line_matcher!(r"^.*: 1: cd: can't cd to (.*)", |m| Ok(Some(Box::new(DirectoryNonExistant(m.get(1).unwrap().as_str().to_string()))))),
    regex_line_matcher!(r"^/bin/bash: (.*): command not found", command_missing),
    regex_line_matcher!(r"^bash: ([^ ]+): command not found", command_missing),
    regex_line_matcher!(r"^env: ‘(.*)’: No such file or directory", interpreter_missing),
    regex_line_matcher!(r"^/bin/bash: .*: (.*): bad interpreter: No such file or directory", interpreter_missing),
    // SH Errors
    regex_line_matcher!(r"^.*: [0-9]+: exec: (.*): not found", command_missing),
    regex_line_matcher!(r"^.*: [0-9]+: (.*): not found", command_missing),
    regex_line_matcher!(r"^/usr/bin/env: [‘'](.*)['’]: No such file or directory", command_missing),
    regex_line_matcher!(r"^make\[[0-9]+\]: (.*): Command not found", command_missing),
    regex_line_matcher!(r"^make: (.*): Command not found", command_missing),
    regex_line_matcher!(r"^make: (.*): No such file or directory", command_missing),
    regex_line_matcher!(r"^xargs: (.*): No such file or directory", command_missing),
    regex_line_matcher!(r"^make\[[0-9]+\]: ([^/ :]+): No such file or directory", command_missing),
    regex_line_matcher!(r"^.*: failed to exec '(.*)': No such file or directory", command_missing),
    regex_line_matcher!(r"^No package '([^']+)' found", pkg_config_missing),
    regex_line_matcher!(r"^--\s* No package '([^']+)' found", pkg_config_missing),
    regex_line_matcher!(
        r"^\-\- Please install Git, make sure it is in your path, and then try again.",
        |_| Ok(Some(Box::new(MissingCommand("git".to_string()))))
    ),
    regex_line_matcher!(
        r#"^\+ERROR:  could not access file "(.*)": No such file or directory"#,
        |m| Ok(Some(Box::new(MissingPostgresExtension(m.get(1).unwrap().as_str().to_string()))))
    ),
    regex_line_matcher!(
        r#"^configure: error: (Can't|Cannot) find "(.*)" in your PATH.*"#,
        |m| Ok(Some(Box::new(MissingCommand(m.get(2).unwrap().as_str().to_string()))))
    ),
    regex_line_matcher!(
        r"^configure: error: Cannot find (.*) in your system path",
        |m| Ok(Some(Box::new(MissingCommand(m.get(1).unwrap().as_str().to_string()))))
    ),
    regex_line_matcher!(
        r#"^> Cannot run program "(.*)": error=2, No such file or directory"#,
        |m| Ok(Some(Box::new(MissingCommand(m.get(1).unwrap().as_str().to_string()))))
    ),
    regex_line_matcher!(r"^(.*) binary '(.*)' not available .*", |m| Ok(Some(Box::new(MissingCommand(m.get(2).unwrap().as_str().to_string()))))),
    regex_line_matcher!(r"^An error has occurred: FatalError: git failed\. Is it installed, and are you in a Git repository directory\?",
     |_| Ok(Some(Box::new(MissingCommand("git".to_string()))))),
    regex_line_matcher!("^Please install '(.*)' seperately and try again.", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))),
    regex_line_matcher!(
        r"^> A problem occurred starting process 'command '(.*)''", |m| Ok(Some(Box::new(MissingCommand(m.get(1).unwrap().as_str().to_string()))))
    ),
    regex_line_matcher!(
        r"^vcver.scm.git.GitCommandError: 'git .*' returned an error code 127",
        |_| Ok(Some(Box::new(MissingCommand("git".to_string()))))
    ),
    Box::new(MultiLineConfigureErrorMatcher),
    Box::new(MultiLinePerlMissingModulesErrorMatcher),
    Box::new(MultiLineVignetteErrorMatcher),
    regex_line_matcher!(r"^configure: error: No package '([^']+)' found", pkg_config_missing),
    regex_line_matcher!(r"^configure: error: (doxygen|asciidoc) is not available and maintainer mode is enabled", |m| Ok(Some(Box::new(MissingCommand(m.get(1).unwrap().as_str().to_string()))))),
    regex_line_matcher!(r"^configure: error: Documentation enabled but rst2html not found.", |_| Ok(Some(Box::new(MissingCommand("rst2html".to_string()))))),
    regex_line_matcher!(r"^cannot run pkg-config to check .* version at (.*) line [0-9]+\.", |_| Ok(Some(Box::new(MissingCommand("pkg-config".to_string()))))),
    regex_line_matcher!(r"^Error: pkg-config not found!", |_| Ok(Some(Box::new(MissingCommand("pkg-config".to_string()))))),
    regex_line_matcher!(r"^\*\*\* pkg-config (.*) or newer\. You can download pkg-config", |m| Ok(Some(Box::new(MissingVagueDependency {
        name: "pkg-config".to_string(),
        minimum_version: Some(m.get(1).unwrap().as_str().to_string()),
        url: None,
        current_version: None
    })))),
    // Tox
    regex_line_matcher!(r"^ERROR: InterpreterNotFound: (.*)", |m| Ok(Some(Box::new(MissingCommand(m.get(1).unwrap().as_str().to_string()))))),
    regex_line_matcher!(r"^ERROR: unable to find python", |_| Ok(Some(Box::new(MissingCommand("python".to_string()))))),
    regex_line_matcher!(r"^ ERROR: BLAS not found!", |_| Ok(Some(Box::new(MissingLibrary("blas".to_string()))))),
    Box::new(AutoconfUnexpectedMacroMatcher),
    regex_line_matcher!(r"^\./configure: [0-9]+: \.: Illegal option .*"),
    regex_line_matcher!(r"^Requested '(.*)' but version of ([^ ]+) is ([^ ]+)", pkg_config_missing),
    regex_line_matcher!(r"^.*configure: error: Package requirements \((.*)\) were not met:", pkg_config_missing),
    regex_line_matcher!(r"^configure: error: [a-z0-9_-]+-pkg-config (.*) couldn't be found", pkg_config_missing),
    regex_line_matcher!(r#"^configure: error: C preprocessor "/lib/cpp" fails sanity check"#),
    regex_line_matcher!(r"^configure: error: .*\. Please install (bison|flex)", |m| Ok(Some(Box::new(MissingCommand(m.get(1).unwrap().as_str().to_string()))))),
    regex_line_matcher!(r"^configure: error: No C\# compiler found. You need to install either mono \(>=(.*)\) or \.Net", |_| Ok(Some(Box::new(MissingCSharpCompiler)))),
    regex_line_matcher!(r"^configure: error: No C\# compiler found", |_| Ok(Some(Box::new(MissingCSharpCompiler)))),
    regex_line_matcher!(r"^error: can't find Rust compiler", |_| Ok(Some(Box::new(MissingRustCompiler)))),
    regex_line_matcher!(r"^Found no assembler", |_| Ok(Some(Box::new(MissingAssembler)))),
    regex_line_matcher!(r"^error: failed to get `(.*)` as a dependency of package `(.*)`", |m| Ok(Some(Box::new(MissingCargoCrate::simple(m.get(1).unwrap().as_str().to_string()))))),
    regex_line_matcher!(r"^configure: error: (.*) requires libkqueue \(or system kqueue\). .*", |_| Ok(Some(Box::new(MissingPkgConfig::simple("libkqueue".to_string()))))),
    regex_line_matcher!(r"^Did not find pkg-config by name 'pkg-config'", |_| Ok(Some(Box::new(MissingCommand("pkg-config".to_string()))))),
    regex_line_matcher!(r"^configure: error: Required (.*) binary is missing. Please install (.*).", |m| Ok(Some(Box::new(MissingCommand(m.get(1).unwrap().as_str().to_string()))))),
    regex_line_matcher!(r#".*meson.build:([0-9]+):([0-9]+): ERROR: Dependency "(.*)" not found"#, |m| Ok(Some(Box::new(MissingPkgConfig::simple(m.get(3).unwrap().as_str().to_string()))))),
    regex_line_matcher!(r".*meson.build:([0-9]+):([0-9]+): ERROR: Problem encountered: No XSLT processor found, .*", |_| Ok(Some(Box::new(MissingVagueDependency::simple("xsltproc"))))),
    regex_line_matcher!(r".*meson.build:([0-9]+):([0-9]+): Unknown compiler\(s\): \[\['(.*)'.*\]", |m| Ok(Some(Box::new(MissingCommand(m.get(3).unwrap().as_str().to_string()))))),
    regex_line_matcher!(".*meson.build:([0-9]+):([0-9]+): ERROR: python3 \"(.*)\" missing", |m| Ok(Some(Box::new(MissingPythonModule {
        module: m.get(3).unwrap().as_str().to_string(),
        python_version: Some(3),
        minimum_version: None,
    })))),
    regex_line_matcher!(".*meson.build:([0-9]+):([0-9]+): ERROR: Program \'(.*)\' not found", |m| Ok(Some(Box::new(MissingCommand(m.get(3).unwrap().as_str().to_string()))))),
    regex_line_matcher!(".*meson.build:([0-9]+):([0-9]+): ERROR: Git program not found, .*", |_| Ok(Some(Box::new(MissingCommand("git".to_string()))))),
    regex_line_matcher!(".*meson.build:([0-9]+):([0-9]+): ERROR: C header \'(.*)\' not found", |m| Ok(Some(Box::new(MissingCHeader::new(m.get(3).unwrap().as_str().to_string()))))),
    regex_line_matcher!(r"^configure: error: (.+\.h) could not be found\. Please set CPPFLAGS\.", |m| Ok(Some(Box::new(MissingCHeader::new(m.get(1).unwrap().as_str().to_string()))))),
    regex_line_matcher!(r".*meson.build:([0-9]+):([0-9]+): ERROR: Unknown compiler\(s\): \['(.*)'\]", |m| Ok(Some(Box::new(MissingCommand(m.get(3).unwrap().as_str().to_string()))))),
    regex_line_matcher!(".*meson.build:([0-9]+):([0-9]+): ERROR: Dependency \"(.*)\" not found, tried pkgconfig", |m| Ok(Some(Box::new(MissingPkgConfig::simple(m.get(3).unwrap().as_str().to_string()))))),
    regex_line_matcher!(r#".*meson.build:([0-9]+):([0-9]+): ERROR: Could not execute Vala compiler "(.*)""#, |m| Ok(Some(Box::new(MissingCommand(m.get(3).unwrap().as_str().to_string()))))),
    regex_line_matcher!(r".*meson.build:([0-9]+):([0-9]+): ERROR: python3 is missing modules: (.*)", |m| Ok(Some(Box::new(MissingPythonModule::simple(m.get(1).unwrap().as_str().to_string()))))),
    regex_line_matcher!(r".*meson.build:([0-9]+):([0-9]+): ERROR: Invalid version of dependency, need '([^']+)' \['>=\s*([^']+)'\] found '([^']+)'\.", |m| Ok(Some(Box::new(MissingPkgConfig::new(m.get(3).unwrap().as_str().to_string(), Some(m.get(4).unwrap().as_str().to_string())))))),
    regex_line_matcher!(".*meson.build:([0-9]+):([0-9]+): ERROR: C shared or static library '(.*)' not found", |m| Ok(Some(Box::new(MissingLibrary(m.get(3).unwrap().as_str().to_string()))))),
    regex_line_matcher!(".*meson.build:([0-9]+):([0-9]+): ERROR: C\\+\\++ shared or static library '(.*)' not found", |m| Ok(Some(Box::new(MissingLibrary(m.get(3).unwrap().as_str().to_string()))))),
    regex_line_matcher!(".*meson.build:([0-9]+):([0-9]+): ERROR: Pkg-config binary for machine .* not found. Giving up.", |_| Ok(Some(Box::new(MissingCommand("pkg-config".to_string()))))),
    regex_line_matcher!(".*meson.build([0-9]+):([0-9]+): ERROR: Problem encountered: (.*) require (.*) >= (.*), (.*) which were not found.", |m| Ok(Some(Box::new(MissingVagueDependency{name: m.get(4).unwrap().as_str().to_string(), current_version: None, url: None, minimum_version: Some(m.get(5).unwrap().as_str().to_string())})))),
    regex_line_matcher!(".*meson.build([0-9]+):([0-9]+): ERROR: Problem encountered: (.*) is required to .*", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(4).unwrap().as_str()))))),
    regex_line_matcher!(r"^ERROR: (.*) is not installed\. Install at least (.*) version (.+) to continue\.", |m| Ok(Some(Box::new(MissingVagueDependency {
        name: m.get(1).unwrap().as_str().to_string(),
        minimum_version: Some(m.get(3).unwrap().as_str().to_string()),
        current_version: None,
        url: None,
    })))),
    regex_line_matcher!(r"^configure: error: Library requirements \((.*)\) not met\.", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))),
    regex_line_matcher!(r"^configure: error: (.*) is missing -- (.*)", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))),
    regex_line_matcher!(r"^configure: error: Cannot find (.*), check (.*)", |m| Ok(Some(Box::new(MissingVagueDependency {
        name: m.get(1).unwrap().as_str().to_string(),
        url: Some(m.get(2).unwrap().as_str().to_string()),
        minimum_version: None,
        current_version: None
    })))),
    regex_line_matcher!(r"^configure: error: \*\*\* Unable to find (.* library)", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))),
    regex_line_matcher!(r"^configure: error: unable to find (.*)\.", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))),
    regex_line_matcher!(r"^configure: error: Perl Module (.*) not available", |m| Ok(Some(Box::new(MissingPerlModule::simple(m.get(1).unwrap().as_str()))))),
    regex_line_matcher!(r"(.*) was not found in your path\. Please install (.*)", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))),
    regex_line_matcher!(r"^configure: error: Please install (.*) >= (.*)", |m| Ok(Some(Box::new(MissingVagueDependency {
        name: m.get(1).unwrap().as_str().to_string(),
        minimum_version: Some(m.get(2).unwrap().as_str().to_string()),
        current_version: None,
        url: None
    })))),
    regex_line_matcher!(
        r"^configure: error: the required package (.*) is not installed", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))),
    regex_line_matcher!(r"^configure: error: \*\*\* (.*) >= (.*) not installed.*", |m| Ok(Some(Box::new(MissingVagueDependency {
        name: m.get(1).unwrap().as_str().to_string(),
        minimum_version: Some(m.get(2).unwrap().as_str().to_string()),
        current_version: None,
        url: None
    })))),
    regex_line_matcher!(r"^configure: error: you should install (.*) first", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))),
    regex_line_matcher!(r"^configure: error: cannot locate (.*) >= (.*)", |m| Ok(Some(Box::new(MissingVagueDependency {
        name: m.get(1).unwrap().as_str().to_string(),
        minimum_version: Some(m.get(2).unwrap().as_str().to_string()),
        current_version: None,
        url: None
    })))),
    regex_line_matcher!(r"^configure: error: !!! Please install (.*) !!!", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))),
    regex_line_matcher!(r"^configure: error: (.*) version (.*) or higher is required", |m| Ok(Some(Box::new(MissingVagueDependency {
        name: m.get(1).unwrap().as_str().to_string(),
        minimum_version: Some(m.get(2).unwrap().as_str().to_string()),
        current_version: None,
        url: None
    })))),
    regex_line_matcher!(r"^configure.(ac|in):[0-9]+: error: libtool version (.*) or higher is required", |m| Ok(Some(Box::new(MissingVagueDependency {
        name: m.get(2).unwrap().as_str().to_string(),
        minimum_version: Some(m.get(3).unwrap().as_str().to_string()),
        current_version: None,
        url: None
    })))),
    regex_line_matcher!(r"configure: error: ([^ ]+) ([^ ]+) or better is required.*", |m| Ok(Some(Box::new(MissingVagueDependency {
        name: m.get(1).unwrap().as_str().to_string(),
        minimum_version: Some(m.get(2).unwrap().as_str().to_string()),
        current_version: None,
        url: None
    })))),
    regex_line_matcher!(r"configure: error: ([^ ]+) ([^ ]+) or greater is required.*", |m| Ok(Some(Box::new(MissingVagueDependency {
        name: m.get(1).unwrap().as_str().to_string(),
        minimum_version: Some(m.get(2).unwrap().as_str().to_string()),
        current_version: None,
        url: None
    })))),
    regex_line_matcher!(r"configure: error: ([^ ]+) or greater is required.*", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))),
    regex_line_matcher!(
        r"configure: error: (.*) library is required",
        |m| Ok(Some(Box::new(MissingLibrary(m.get(1).unwrap().as_str().to_string()))))),
    regex_line_matcher!(
        r"configure: error: (.*) library is not installed\.",
        |m| Ok(Some(Box::new(MissingLibrary(m.get(1).unwrap().as_str().to_string()))))),
    regex_line_matcher!(
        r"configure: error: OpenSSL developer library 'libssl-dev' or 'openssl-devel' not installed; cannot continue.",
        |_m| Ok(Some(Box::new(MissingLibrary("ssl".to_string()))))),
    regex_line_matcher!(
        r"configure: error: \*\*\* Cannot find (.*)",
        |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))),
    regex_line_matcher!(
        r"configure: error: (.*) is required to compile .*",
        |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))),

    regex_line_matcher!(
        r"\s*You must have (.*) installed to compile .*\.",
        |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))),

    regex_line_matcher!(
        r"You must install (.*) to compile (.*)",
        |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))),

    regex_line_matcher!(
        r"\*\*\* No (.*) found, please in(s?)tall it \*\*\*",
        |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))),

    regex_line_matcher!(
        r"configure: error: (.*) required, please in(s?)tall it",
        |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))),

    regex_line_matcher!(
        r"\*\* ERROR \*\* : You must have `(.*)' installed on your system\.",
        |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))),

    regex_line_matcher!(
        r"autogen\.sh: ERROR: You must have `(.*)' installed to compile this package\.",
        |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))),

    regex_line_matcher!(
        r"autogen\.sh: You must have (.*) installed\.", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))),

    regex_line_matcher!(
        r"\s*Error! You need to have (.*) installed\.",
        |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))),

    regex_line_matcher!(
        r"(configure: error|\*\*Error\*\*): You must have (.*) installed.*",
        |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(2).unwrap().as_str()))))),

    regex_line_matcher!(
        r"configure: error: (.*) is required for building this package.",
        |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))),

    regex_line_matcher!(
        r"configure: error: (.*) is required to build (.*)",
        |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))),

    regex_line_matcher!(
        r"configure: error: (.*) is required",
        |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))),

    regex_line_matcher!(
        r"configure: error: (.*) is required for (.*)",
        |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))),

    regex_line_matcher!(
        r"configure: error: \*\*\* (.*) is required\.",
        |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))),

    regex_line_matcher!(
        r"configure: error: (.*) is required, please get it from (.*)",
        |m| Ok(Some(Box::new(MissingVagueDependency{
            name: m.get(1).unwrap().as_str().to_string(),
            url: Some(m.get(2).unwrap().as_str().to_string()),
            minimum_version: None, current_version: None})))),
    regex_line_matcher!(
        r".*meson.build:\d+:\d+: ERROR: Assert failed: (.*) support explicitly required, but (.*) not found",
        |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))),

    regex_line_matcher!(
        r"configure: error: .*, (lib[^ ]+) is required",
        |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))),

    regex_line_matcher!(
        r"dh: Unknown sequence --(.*) \(options should not come before the sequence\)",
        |_| Ok(Some(Box::new(DhWithOrderIncorrect)))),
    regex_line_matcher!(
        r"(dh: |dh_.*: error: )Compatibility levels before ([0-9]+) are no longer supported \(level ([0-9]+) requested\)",
        |m| {
            let l1 = m.get(2).unwrap().as_str().parse().unwrap();
            let l2 = m.get(3).unwrap().as_str().parse().unwrap();
            Ok(Some(Box::new(UnsupportedDebhelperCompatLevel::new(l1, l2))))
        }
    ),
    regex_line_matcher!(r"\{standard input\}: Error: (.*)"),
    regex_line_matcher!(r"dh: Unknown sequence (.*) \(choose from: .*\)"),
    regex_line_matcher!(r".*: .*: No space left on device", |_m| Ok(Some(Box::new(NoSpaceOnDevice)))),
    regex_line_matcher!(r"^No space left on device.", |_m| Ok(Some(Box::new(NoSpaceOnDevice)))),
    regex_line_matcher!(
        r".*Can't locate (.*).pm in @INC \(you may need to install the (.*) module\) \(@INC contains: (.*)\) at .* line [0-9]+\.",
        |m| {
            let path = format!("{}.pm", m.get(1).unwrap().as_str());
            let inc = m.get(3).unwrap().as_str().split(' ').map(|s| s.to_string()).collect::<Vec<_>>();

            Ok(Some(Box::new(MissingPerlModule{ filename: Some(path), module: m.get(2).unwrap().as_str().to_string(), minimum_version: None, inc: Some(inc)})))
        }
    ),
    regex_line_matcher!(
        r".*Can't locate (.*).pm in @INC \(you may need to install the (.*) module\) \(@INC contains: (.*)\)\.",
        |m| {
            let path = format!("{}.pm", m.get(1).unwrap().as_str());
            let inc = m.get(3).unwrap().as_str().split(' ').map(|s| s.to_string()).collect::<Vec<_>>();

            Ok(Some(Box::new(MissingPerlModule{ filename: Some(path), module: m.get(2).unwrap().as_str().to_string(), inc: Some(inc), minimum_version: None })))
        }
    ),
    regex_line_matcher!(
        r"\[DynamicPrereqs\] Can't locate (.*) at inline delegation in .*",
        |m| Ok(Some(Box::new(MissingPerlModule::simple(m.get(1).unwrap().as_str()))))
    ),
    regex_line_matcher!(
        r#"Can't locate object method "(.*)" via package "(.*)" \(perhaps you forgot to load "(.*)"\?\) at .*.pm line [0-9]+\."#,
        |m| Ok(Some(Box::new(MissingPerlModule::simple(m.get(2).unwrap().as_str()))))
    ),
    regex_line_matcher!(
        r">\(error\): Could not expand \[(.*)'",
        |m| Ok(Some(Box::new(MissingPerlModule::simple(m.get(1).unwrap().as_str().trim().trim_matches('\'')))))),

    regex_line_matcher!(
        r"\[DZ\] could not load class (.*) for license (.*)",
        |m| Ok(Some(Box::new(MissingPerlModule::simple(m.get(1).unwrap().as_str()))))),

    regex_line_matcher!(
        r"\- ([^\s]+)\s+\.\.\.missing. \(would need (.*)\)",
        |m| Ok(Some(Box::new(MissingPerlModule {
            filename: None,
            module: m.get(1).unwrap().as_str().to_string(),
            inc: None,
            minimum_version: Some(m.get(2).unwrap().as_str().to_string()),
        })))),

    regex_line_matcher!(
        r"Required plugin bundle ([^ ]+) isn't installed.",
        |m| Ok(Some(Box::new(MissingPerlModule::simple(m.get(1).unwrap().as_str()))))),

    regex_line_matcher!(
        r"Required plugin ([^ ]+) isn't installed.",
        |m| Ok(Some(Box::new(MissingPerlModule::simple(m.get(1).unwrap().as_str()))))),

    regex_line_matcher!(
        r".*Can't locate (.*) in @INC \(@INC contains: (.*)\) at .* line .*.",
        |m| {
            let inc = m.get(2).unwrap().as_str().split(' ').map(|s| s.to_string()).collect::<Vec<_>>();
            Ok(Some(Box::new(MissingPerlFile::new(m.get(1).unwrap().as_str().to_string(), Some(inc)))))
        }),

    regex_line_matcher!(
        r"Can't find author dependency ([^ ]+) at (.*) line ([0-9]+).",
        |m| Ok(Some(Box::new(MissingPerlModule::simple(m.get(1).unwrap().as_str()))))),

    regex_line_matcher!(
        r"Can't find author dependency ([^ ]+) version (.*) at (.*) line ([0-9]+).",
        |m| Ok(Some(Box::new(MissingPerlModule {
            filename: None,
            module: m.get(1).unwrap().as_str().to_string(),
            inc: None,
            minimum_version: Some(m.get(2).unwrap().as_str().to_string()),
        })))),
    regex_line_matcher!(
        r"> Could not find (.*)\. Please check that (.*) contains a valid JDK installation.",
        |m| Ok(Some(Box::new(MissingJDKFile::new(m.get(2).unwrap().as_str().to_string(), m.get(1).unwrap().as_str().to_string()))))),

    regex_line_matcher!(
        r"> Could not find (.*)\. Please check that (.*) contains a valid \(and compatible\) JDK installation.",
        |m| Ok(Some(Box::new(MissingJDKFile::new(m.get(2).unwrap().as_str().to_string(), m.get(1).unwrap().as_str().to_string()))))),

    regex_line_matcher!(
        r"> Kotlin could not find the required JDK tools in the Java installation '(.*)' used by Gradle. Make sure Gradle is running on a JDK, not JRE.",
        |m| Ok(Some(Box::new(MissingJDK::new(m.get(1).unwrap().as_str().to_string()))))),

    regex_line_matcher!(
        r"> JDK_5 environment variable is not defined. It must point to any JDK that is capable to compile with Java 5 target \((.*)\)",
        |m| Ok(Some(Box::new(MissingJDK::new(m.get(1).unwrap().as_str().to_string()))))),

    regex_line_matcher!(
        r"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.",
        |_| Ok(Some(Box::new(MissingJRE)))),

    regex_line_matcher!(
        r#"Error: environment variable "JAVA_HOME" must be set to a JDK \(>= v(.*)\) installation directory"#,
        |m| Ok(Some(Box::new(MissingJDK::new(m.get(1).unwrap().as_str().to_string()))))),

    regex_line_matcher!(
        r"(?:/usr/bin/)?install: cannot create regular file '(.*)': No such file or directory",
        file_not_found
    ),
    regex_line_matcher!(
        r"Cannot find source directory \((.*)\)",
        file_not_found
    ),
    regex_line_matcher!(
        r"python[0-9.]*: can't open file '(.*)': \[Errno 2\] No such file or directory",
        file_not_found
    ),
    regex_line_matcher!(
        r"^error: \[Errno 2\] No such file or directory: '(.*)'",
        |m| file_not_found_maybe_executable(m.get(1).unwrap().as_str())
    ),
    regex_line_matcher!(
        r".*:[0-9]+:[0-9]+: ERROR: <ExternalProgram 'python3' -> \['/usr/bin/python3'\]> is not a valid python or it is missing setuptools",
        |_| Ok(Some(Box::new(MissingPythonDistribution {
            distribution: "setuptools".to_string(),
            python_version: Some(3),
            minimum_version: None,
        })))
    ),
    regex_line_matcher!(r"OSError: \[Errno 28\] No space left on device", |_| Ok(Some(Box::new(NoSpaceOnDevice)))),
    // python:setuptools_scm
    regex_line_matcher!(
        r"^LookupError: setuptools-scm was unable to detect version for '.*'\.",
        |_| Ok(Some(Box::new(SetuptoolScmVersionIssue)))
    ),
    regex_line_matcher!(
        r"^LookupError: setuptools-scm was unable to detect version for .*\.",
        |_| Ok(Some(Box::new(SetuptoolScmVersionIssue)))
    ),
    regex_line_matcher!(r"^OSError: 'git' was not found", |_| Ok(Some(Box::new(MissingCommand("git".to_string()))))),
    regex_line_matcher!(r"^OSError: No such file (.*)", |m| file_not_found_maybe_executable(m.get(1).unwrap().as_str())),
    regex_line_matcher!(
        r"^Could not open '(.*)': No such file or directory at /usr/share/perl/[0-9.]+/ExtUtils/MM_Unix.pm line [0-9]+.",
        |m| Ok(Some(Box::new(MissingPerlFile::new(m.get(1).unwrap().as_str().to_string(), None))))
    ),
    regex_line_matcher!(
        r#"^Can't open perl script "(.*)": No such file or directory"#,
        |m| Ok(Some(Box::new(MissingPerlFile::new(m.get(1).unwrap().as_str().to_string(), None))))),
    // Maven
    regex_line_matcher!(
        format!("{}{}", MAVEN_ERROR_PREFIX, r"Failed to execute goal on project .*: \x1b\[1;31mCould not resolve dependencies for project .*: The following artifacts could not be resolved: (.*): Could not find artifact (.*) in (.*) \((.*)\)\x1b\[m -> \x1b\[1m\[Help 1\]\x1b\[m").as_str(), maven_missing_artifact),

    regex_line_matcher!(
        format!("{}{}", MAVEN_ERROR_PREFIX, r"Failed to execute goal on project .*: \x1b\[1;31mCould not resolve dependencies for project .*: Could not find artifact (.*)\x1b\[m .*").as_str(),
        maven_missing_artifact
    ),

    regex_line_matcher!(
        format!("{}{}", MAVEN_ERROR_PREFIX, r"Failed to execute goal on project .*: Could not resolve dependencies for project .*: The following artifacts could not be resolved: (.*): Cannot access central \(https://repo\.maven\.apache\.org/maven2\) in offline mode and the artifact .* has not been downloaded from it before..*").as_str(), maven_missing_artifact
    ),
    regex_line_matcher!(
        format!("{}{}", MAVEN_ERROR_PREFIX, r"Unresolveable build extension: Plugin (.*) or one of its dependencies could not be resolved: Cannot access central \(https://repo.maven.apache.org/maven2\) in offline mode and the artifact .* has not been downloaded from it before. @").as_str(), |m| Ok(Some(Box::new(MissingMavenArtifacts(vec![m.get(1).unwrap().as_str().to_string()]))))),
    regex_line_matcher!(
        format!("{}{}", MAVEN_ERROR_PREFIX, r"Non-resolvable import POM: Cannot access central \(https://repo.maven.apache.org/maven2\) in offline mode and the artifact (.*) has not been downloaded from it before. @ line [0-9]+, column [0-9]+").as_str(), maven_missing_artifact),
    regex_line_matcher!(
        r"\[FATAL\] Non-resolvable parent POM for .*: Cannot access central \(https://repo.maven.apache.org/maven2\) in offline mode and the artifact (.*) has not been downloaded from it before. .*", maven_missing_artifact),
    regex_line_matcher!(
        format!("{}{}", MAVEN_ERROR_PREFIX,r"Plugin (.*) or one of its dependencies could not be resolved: Cannot access central \(https://repo.maven.apache.org/maven2\) in offline mode and the artifact .* has not been downloaded from it before. -> \[Help 1\]").as_str(), |m| Ok(Some(Box::new(MissingMavenArtifacts(vec![m.get(1).unwrap().as_str().to_string()]))))),
    regex_line_matcher!(
        format!("{}{}", MAVEN_ERROR_PREFIX, r"Plugin (.+) or one of its dependencies could not be resolved: Failed to read artifact descriptor for (.*): (.*)").as_str(), |m| Ok(Some(Box::new(MissingMavenArtifacts(vec![m.get(1).unwrap().as_str().to_string()]))))),
    regex_line_matcher!(
        format!("{}{}", MAVEN_ERROR_PREFIX, r"Failed to execute goal on project .*: Could not resolve dependencies for project .*: Cannot access .* \([^\)]+\) in offline mode and the artifact (.*) has not been downloaded from it before. -> \[Help 1\]").as_str(), maven_missing_artifact),
    regex_line_matcher!(
        format!("{}{}", MAVEN_ERROR_PREFIX, r"Failed to execute goal on project .*: Could not resolve dependencies for project .*: Cannot access central \(https://repo.maven.apache.org/maven2\) in offline mode and the artifact (.*) has not been downloaded from it before..*").as_str(), maven_missing_artifact),
    regex_line_matcher!(format!("{}{}", MAVEN_ERROR_PREFIX, "Failed to execute goal (.*) on project (.*): (.*)").as_str(), |_| Ok(None)),
    regex_line_matcher!(
        format!("{}{}", MAVEN_ERROR_PREFIX, r"Error resolving version for plugin \'(.*)\' from the repositories \[.*\]: Plugin not found in any plugin repository -> \[Help 1\]").as_str(),
        |m| Ok(Some(Box::new(MissingMavenArtifacts(vec![m.get(1).unwrap().as_str().to_string()]))))
    ),
    regex_line_matcher!(
        r"E: eatmydata: unable to find '(.*)' in PATH",
        |m| Ok(Some(Box::new(MissingCommand(m.get(1).unwrap().as_str().to_string()))))
    ),
    regex_line_matcher!(
        r"'(.*)' not found in PATH at (.*) line ([0-9]+)\.",
        |m| Ok(Some(Box::new(MissingCommand(m.get(1).unwrap().as_str().to_string()))))
    ),
    regex_line_matcher!(
        r"/usr/bin/eatmydata: [0-9]+: exec: (.*): not found",
        command_missing
    ),
    regex_line_matcher!(
        r"/usr/bin/eatmydata: [0-9]+: exec: (.*): Permission denied",
        |m| Ok(Some(Box::new(NotExecutableFile(m.get(1).unwrap().as_str().to_string()))))
    ),
    regex_line_matcher!(
        r#"(.*): exec: "(.*)": executable file not found in \$PATH"#,
        |m| Ok(Some(Box::new(MissingCommand(m.get(2).unwrap().as_str().to_string()))))
    ),
    regex_line_matcher!(
        r#"Can't exec "(.*)": No such file or directory at (.*) line ([0-9]+)\."#,
        command_missing
    ),
    regex_line_matcher!(
        r"dh_missing: (warning: )?(.*) exists in debian/.* but is not installed to anywhere",
        |m| Ok(Some(Box::new(DhMissingUninstalled(m.get(2).unwrap().as_str().to_string()))))
    ),
    regex_line_matcher!(r"dh_link: link destination (.*) is a directory",
                        |m| Ok(Some(Box::new(DhLinkDestinationIsDirectory(m.get(1).unwrap().as_str().to_string()))))),
    regex_line_matcher!(r"I/O error : Attempt to load network entity (.*)",
                        |m| Ok(Some(Box::new(MissingXmlEntity::new(m.get(1).unwrap().as_str().to_string()))))),
    regex_line_matcher!(r"ccache: error: (.*)",
    |m| Ok(Some(Box::new(CcacheError(m.get(1).unwrap().as_str().to_string()))))),
    regex_line_matcher!(
        r"dh: The --until option is not supported any longer \(#932537\). Use override targets instead.",
        |_| Ok(Some(Box::new(DhUntilUnsupported::new())))
    ),
    regex_line_matcher!(
        r"dh: unable to load addon (.*): (.*) did not return a true value at \(eval 11\) line ([0-9]+).",
        |m| Ok(Some(Box::new(DhAddonLoadFailure::new(m.get(1).unwrap().as_str().to_string(), m.get(2).unwrap().as_str().to_string()))))
    ),
    regex_line_matcher!(
        "ERROR: dependencies (.*) are not available for package [‘'](.*)['’]",
        r_missing_package
    ),
    regex_line_matcher!(
        "ERROR: dependency [‘'](.*)['’] is not available for package [‘'](.*)[’']",
        r_missing_package
    ),
    regex_line_matcher!(
        r"Error in library\(.*\) : there is no package called \'(.*)\'",
        r_missing_package
    ),
    regex_line_matcher!(r"Error in .* : there is no package called \'(.*)\'", r_missing_package),
    regex_line_matcher!(r"there is no package called \'(.*)\'", r_missing_package),
    regex_line_matcher!(
        r"  namespace ‘(.*)’ ([^ ]+) is being loaded, but >= ([^ ]+) is required",
        |m| Ok(Some(Box::new(MissingRPackage{ package: m.get(1).unwrap().as_str().to_string(), minimum_version: Some(m.get(3).unwrap().as_str().to_string())})))
    ),
    regex_line_matcher!(
        r"  namespace ‘(.*)’ ([^ ]+) is already loaded, but >= ([^ ]+) is required",
        |m| Ok(Some(Box::new(MissingRPackage{package: m.get(1).unwrap().as_str().to_string(), minimum_version: Some(m.get(3).unwrap().as_str().to_string())})))
    ),
    regex_line_matcher!(r"b\'convert convert: Unable to read font \((.*)\) \[No such file or directory\]\.\\n\'",
     file_not_found),
    regex_line_matcher!(r"mv: cannot stat \'(.*)\': No such file or directory", file_not_found),
    regex_line_matcher!(r"mv: cannot move \'.*\' to \'(.*)\': No such file or directory", file_not_found),
    regex_line_matcher!(
        r"(/usr/bin/install|mv): will not overwrite just-created \'(.*)\' with \'(.*)\'",
        |_| Ok(None)
    ),
    regex_line_matcher!(r"^IOError: \[Errno 2\] No such file or directory: \'(.*)\'", |m| file_not_found_maybe_executable(m.get(1).unwrap().as_str())),
    regex_line_matcher!(r"^error: \[Errno 2\] No such file or directory: \'(.*)\'", |m| file_not_found_maybe_executable(m.get(1).unwrap().as_str())),
    regex_line_matcher!(r"^E   IOError: \[Errno 2\] No such file or directory: \'(.*)\'", |m| file_not_found_maybe_executable(m.get(1).unwrap().as_str())),
    regex_line_matcher!("FAIL\t(.+\\/.+\\/.+)\t([0-9.]+)s", |_| Ok(None)),
    regex_line_matcher!(
        r#"dh_(.*): Cannot find \(any matches for\) "(.*)" \(tried in (.*)\)"#,
        |m| Ok(Some(Box::new(DebhelperPatternNotFound {
            pattern: m.get(2).unwrap().as_str().to_string(),
            tool: m.get(1).unwrap().as_str().to_string(),
            directories: m.get(3).unwrap().as_str().split(',').map(|s| s.trim().to_string()).collect(),
        })))
    ),
    regex_line_matcher!(
        r#"Can't exec "(.*)": No such file or directory at /usr/share/perl5/Debian/Debhelper/Dh_Lib.pm line [0-9]+."#,
        command_missing
    ),
    regex_line_matcher!(
        r#"Can\'t exec "(.*)": Permission denied at (.*) line [0-9]+\."#,
        |m| Ok(Some(Box::new(NotExecutableFile(m.get(1).unwrap().as_str().to_string()))))
    ),
    regex_line_matcher!(
        r"/usr/bin/fakeroot: [0-9]+: (.*): Permission denied",
        |m| Ok(Some(Box::new(NotExecutableFile(m.get(1).unwrap().as_str().to_string()))))
    ),
    regex_line_matcher!(r".*: error: (.*) command not found", command_missing),
    regex_line_matcher!(r"error: command '(.*)' failed: No such file or directory",
     command_missing),
    regex_line_matcher!(
        r"dh_install: Please use dh_missing --list-missing/--fail-missing instead",
        |_| Ok(None)
    ),

    regex_line_matcher!(
        r#"dh([^:]*): Please use the third-party "pybuild" build system instead of python-distutils"#,
        |_| Ok(None)
    ),
    // A Python error, but not likely to be actionable. The previous line will have the actual line that failed.
    regex_line_matcher!(r"ImportError: cannot import name (.*)", |_| Ok(None)),
    // Rust ?
    regex_line_matcher!(r"\s*= note: /usr/bin/ld: cannot find -l([^ ]+): .*", |m| Ok(Some(Box::new(MissingLibrary(m.get(1).unwrap().as_str().to_string()))))),
    regex_line_matcher!(r"\s*= note: /usr/bin/ld: cannot find -l([^ ]+)", |m| Ok(Some(Box::new(MissingLibrary(m.get(1).unwrap().as_str().to_string()))))),
    regex_line_matcher!(r"/usr/bin/ld: cannot find -l([^ ]+): .*", |m| Ok(Some(Box::new(MissingLibrary(m.get(1).unwrap().as_str().to_string()))))),
    regex_line_matcher!(r"/usr/bin/ld: cannot find -l([^ ]+)", |m| Ok(Some(Box::new(MissingLibrary(m.get(1).unwrap().as_str().to_string()))))),
    regex_line_matcher!(
        r"Could not find gem \'([^ ]+) \(([^)]+)\)\', which is required by gem.*",
        ruby_missing_gem
    ),
    regex_line_matcher!(
        r"Could not find gem \'([^ \']+)\', which is required by gem.*",
        |m| Ok(Some(Box::new(MissingRubyGem::simple(m.get(1).unwrap().as_str().to_string()))))
    ),
    regex_line_matcher!(
        r"[^:]+:[0-9]+:in \`to_specs\': Could not find \'(.*)\' \(([^)]+)\) among [0-9]+ total gem\(s\) \(Gem::MissingSpecError\)",
        ruby_missing_gem
    ),
    regex_line_matcher!(
        r"[^:]+:[0-9]+:in \`to_specs\': Could not find \'(.*)\' \(([^)]+)\) - .* \(Gem::MissingSpecVersionError\)",
        ruby_missing_gem
    ),
    regex_line_matcher!(
        r"[^:]+:[0-9]+:in \`block in verify_gemfile_dependencies_are_found\!\': Could not find gem \'(.*)\' in any of the gem sources listed in your Gemfile\. \(Bundler::GemNotFound\)",
        |m| Ok(Some(Box::new(MissingRubyGem::simple(m.get(1).unwrap().as_str().to_string()))))
    ),
    regex_line_matcher!(
        r"Exception: (.*) not in path[!.]*",
        |m| Ok(Some(Box::new(MissingCommand(m.get(1).unwrap().as_str().to_string()))))
    ),
    regex_line_matcher!(
        r"Exception: Building sdist requires that ([^ ]+) be installed\.",
        |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))
    ),
    regex_line_matcher!(
        r"[^:]+:[0-9]+:in \`find_spec_for_exe\': can\'t find gem (.*) \(([^)]+)\) with executable (.*) \(Gem::GemNotFoundException\)",
        ruby_missing_gem
    ),
    regex_line_matcher!(
        r".?PHP Fatal error:  Uncaught Error: Class \'(.*)\' not found in (.*):([0-9]+)",
        |m| Ok(Some(Box::new(MissingPhpClass::simple(m.get(1).unwrap().as_str().to_string()))))
    ),
    regex_line_matcher!(r"Caused by: java.lang.ClassNotFoundException: (.*)", |m| Ok(Some(Box::new(MissingJavaClass::simple(m.get(1).unwrap().as_str().to_string()))))),
    regex_line_matcher!(
        r"\[(.*)\] \t\t:: (.*)\#(.*);\$\{(.*)\}: not found",
        |m| Ok(Some(Box::new(MissingMavenArtifacts(vec![format!("{}:{}:jar:debian", m.get(2).unwrap().as_str(), m.get(3).unwrap().as_str())]))))
    ),
    regex_line_matcher!(
        r"Caused by: java.lang.IllegalArgumentException: Cannot find JAR \'(.*)\' required by module \'(.*)\' using classpath or distribution directory \'(.*)\'",
        |_| Ok(None)
    ),
    regex_line_matcher!(
        r".*\.xml:[0-9]+: Unable to find a javac compiler;",
        |_| Ok(Some(Box::new(MissingJavaClass::simple("com.sun.tools.javac.Main".to_string()))))
    ),
    regex_line_matcher!(
        r#"checking for (.*)\.\.\. configure: error: "Cannot check for existence of module (.*) without pkgconf""#,
        |_| Ok(Some(Box::new(MissingCommand("pkgconf".to_string()))))
    ),
    regex_line_matcher!(
        r"configure: error: Could not find '(.*)' in path\.",
        |m| Ok(Some(Box::new(MissingCommand(m.get(1).unwrap().as_str().to_string()))))
    ),
    regex_line_matcher!(
        r"autoreconf was not found; .*",
        |_| Ok(Some(Box::new(MissingCommand("autoreconf".to_string()))))
    ),
    regex_line_matcher!(r"^g\+\+: error: (.*): No such file or directory", file_not_found),
    regex_line_matcher!(r"strip: \'(.*)\': No such file", file_not_found),
    regex_line_matcher!(
        r"Sprockets::FileNotFound: couldn\'t find file \'(.*)\' with type \'(.*)\'",
        |m| Ok(Some(Box::new(MissingSprocketsFile{ name: m.get(1).unwrap().as_str().to_string(), content_type: m.get(2).unwrap().as_str().to_string()})))
    ),
    regex_line_matcher!(
        r#"xdt-autogen: You must have "(.*)" installed. You can get if from"#,
        |m| Ok(Some(Box::new(MissingXfceDependency::new(m.get(1).unwrap().as_str().to_string()))))
    ),
    regex_line_matcher!(
        r"autogen.sh: You must have GNU autoconf installed.",
        |_| Ok(Some(Box::new(MissingCommand("autoconf".to_string()))))
    ),
    regex_line_matcher!(
        r"\s*You must have (autoconf|automake|aclocal|libtool|libtoolize) installed to compile (.*)\.",
        |m| Ok(Some(Box::new(MissingCommand(m.get(1).unwrap().as_str().to_string()))))
    ),
    regex_line_matcher!(
        r"It appears that Autotools is not correctly installed on this system.",
        |_| Ok(Some(Box::new(MissingCommand("autoconf".to_string()))))
    ),
    regex_line_matcher!(
        r"\*\*\* No autoreconf found \*\*\*",
        |_| Ok(Some(Box::new(MissingCommand("autoreconf".to_string()))))
    ),
    regex_line_matcher!(r"You need to install gnome-common module and make.*", |_| Ok(Some(Box::new(GnomeCommonMissing)))),
    regex_line_matcher!(r"You need to install the gnome-common module and make.*", |_| Ok(Some(Box::new(GnomeCommonMissing)))),
    regex_line_matcher!(
        r"You need to install gnome-common from the GNOME (git|CVS|SVN)",
        |_| Ok(Some(Box::new(GnomeCommonMissing)))
    ),
    regex_line_matcher!(
        r"automake: error: cannot open < (.*): No such file or directory",
        |m| Ok(Some(Box::new(MissingAutomakeInput::new(m.get(1).unwrap().as_str().to_string()))))
    ),
    regex_line_matcher!(
        r"configure(|\.in|\.ac):[0-9]+: error: possibly undefined macro: (.*)",
        |m| Ok(Some(Box::new(MissingAutoconfMacro::new(m.get(2).unwrap().as_str().to_string()))))
    ),
    regex_line_matcher!(
        r"configure.(in|ac):[0-9]+: error: macro (.*) is not defined; is a m4 file missing\?",
        |m| Ok(Some(Box::new(MissingAutoconfMacro::new(m.get(2).unwrap().as_str().to_string()))))
    ),
    regex_line_matcher!(
        r"config.status: error: cannot find input file: `(.*)\'",
        |m| Ok(Some(Box::new(MissingConfigStatusInput::new(m.get(1).unwrap().as_str().to_string()))))
    ),
    regex_line_matcher!(
        r"\*\*\*Error\*\*\*: You must have glib-gettext >= (.*) installed.*",
        |m| Ok(Some(Box::new(MissingGnomeCommonDependency::new("glib-gettext".to_string(), Some(m.get(1).unwrap().as_str().to_string())))))
    ),
    regex_line_matcher!(
        r"ERROR: JAVA_HOME is set to an invalid directory: /usr/lib/jvm/default-java/",
        |_| Ok(Some(Box::new(MissingJVM)))
    ),
    regex_line_matcher!(
        r#"Error: The file "MANIFEST" is missing from this distribution\. The MANIFEST lists all files included in the distribution\."#,
        |_| Ok(Some(Box::new(MissingPerlManifest)))
    ),
    regex_line_matcher!(
        r"dh_installdocs: --link-doc not allowed between (.*) and (.*) \(one is arch:all and the other not\)",
        |_| Ok(None)
    ),
    regex_line_matcher!(
        r"dh: unable to load addon systemd: dh: The systemd-sequence is no longer provided in compat >= 11, please rely on dh_installsystemd instead",
        |_| Ok(None)
    ),
    regex_line_matcher!(
        r"dh: The --before option is not supported any longer \(#932537\). Use override targets instead.",
        |_| Ok(None)
    ),
    regex_line_matcher!(r"\(.*\): undefined reference to `(.*)'", |_| Ok(None)),
    regex_line_matcher!("(.*):([0-9]+): undefined reference to `(.*)'", |_| Ok(None)),
    regex_line_matcher!("(.*):([0-9]+): error: undefined reference to '(.*)'", |_| Ok(None)),
    regex_line_matcher!(
        r"\/usr\/bin\/ld:(.*): multiple definition of `*.\'; (.*): first defined here",
        |_| Ok(None)
    ),
    regex_line_matcher!(r".+\.go:[0-9]+: undefined reference to `(.*)'", |_| Ok(None)),
    regex_line_matcher!(r"ar: libdeps specified more than once", |_| Ok(None)),
    regex_line_matcher!(
        r"\/usr\/bin\/ld: .*\(.*\):\(.*\): multiple definition of `*.\'; (.*):\((.*)\) first defined here",
        |_| Ok(None)
    ),
    regex_line_matcher!(
        r"\/usr\/bin\/ld:(.*): multiple definition of `*.\'; (.*):\((.*)\) first defined here",
        |_| Ok(None)
    ),
    regex_line_matcher!(r"\/usr\/bin\/ld: (.*): undefined reference to `(.*)\'", |_| Ok(None)),
    regex_line_matcher!(r"\/usr\/bin\/ld: (.*): undefined reference to symbol \'(.*)\'", |_| Ok(None)),
    regex_line_matcher!(
        r"\/usr\/bin\/ld: (.*): relocation (.*) against symbol `(.*)\' can not be used when making a shared object; recompile with -fPIC",
        |_| Ok(None)
    ),
    regex_line_matcher!(
        "(.*):([0-9]+): multiple definition of `(.*)'; (.*):([0-9]+): first defined here",
        |_| Ok(None)
    ),
    regex_line_matcher!(
        "(dh.*): debhelper compat level specified both in debian/compat and via build-dependency on debhelper-compat",
        |m| Ok(Some(Box::new(DuplicateDHCompatLevel::new(m.get(1).unwrap().as_str().to_string()))))
    ),
    regex_line_matcher!(
        "(dh.*): (error: )?Please specify the compatibility level in debian/compat",
        |m| Ok(Some(Box::new(MissingDHCompatLevel::new(m.get(1).unwrap().as_str().to_string()))))
    ),
    regex_line_matcher!(
        "dh_makeshlibs: The udeb (.*) does not contain any shared libraries but --add-udeb=(.*) was passed!?",
        |_| Ok(None)
    ),
    regex_line_matcher!(
        "dpkg-gensymbols: error: some symbols or patterns disappeared in the symbols file: see diff output below",
        |_| Ok(Some(Box::new(DisappearedSymbols)))
    ),
    regex_line_matcher!(
        r"Failed to copy \'(.*)\': No such file or directory at /usr/share/dh-exec/dh-exec-install-rename line [0-9]+.*",
        file_not_found
    ),
    regex_line_matcher!(r"Invalid gemspec in \[.*\]: No such file or directory - (.*)", command_missing),
    regex_line_matcher!(
        r".*meson.build:[0-9]+:[0-9]+: ERROR: Program\(s\) \[\'(.*)\'\] not found or not executable",
        command_missing
    ),
    regex_line_matcher!(
        r".*meson.build:[0-9]+:[0-9]: ERROR: Git program not found\.",
        |_| Ok(Some(Box::new(MissingCommand("git".to_string()))))
    ),
    regex_line_matcher!(
        r"Failed: [pytest] section in setup.cfg files is no longer supported, change to [tool:pytest] instead.",
        |_| Ok(None)
    ),
    regex_line_matcher!(r"cp: cannot stat \'(.*)\': No such file or directory", file_not_found),
    regex_line_matcher!(r"cp: \'(.*)\' and \'(.*)\' are the same file", |_| Ok(None)),
    regex_line_matcher!(r".?PHP Fatal error: (.*)", |_| Ok(None)),
    regex_line_matcher!(r"sed: no input files", |_| Ok(None)),
    regex_line_matcher!(r"sed: can\'t read (.*): No such file or directory", file_not_found),
    regex_line_matcher!(
        r"ERROR in Entry module not found: Error: Can\'t resolve \'(.*)\' in \'(.*)\'",
        webpack_file_missing
    ),
    regex_line_matcher!(
        r".*:([0-9]+): element include: XInclude error : could not load (.*), and no fallback was found",
        |_| Ok(None)
    ),
    regex_line_matcher!(r"E: Child terminated by signal ‘Terminated’",
     |_| Ok(Some(Box::new(Cancelled)))
     ),
    regex_line_matcher!(r"E: Caught signal ‘Terminated’",
     |_| Ok(Some(Box::new(Cancelled)))
     ),
    regex_line_matcher!(r"E: Failed to execute “(.*)”: No such file or directory", command_missing),
    regex_line_matcher!(r"E ImportError: Bad (.*) executable(\.?)", command_missing),
    regex_line_matcher!(r"E: The Debian version .* cannot be used as an ELPA version.", |_| Ok(None)),
    // ImageMagick
    regex_line_matcher!(
        r"convert convert: Image pixel limit exceeded \(see -limit Pixels\) \(-1\).",
        |_| Ok(None)
    ),
    regex_line_matcher!(r"convert convert: Improper image header \(.*\).", |_| Ok(None)),
    regex_line_matcher!(r"convert convert: invalid primitive argument \([0-9]+\).", |_| Ok(None)),
    regex_line_matcher!(r"convert convert: Unexpected end-of-file \(\)\.", |_| Ok(None)),
    regex_line_matcher!(r"convert convert: Unrecognized option \((.*)\)\.", |_| Ok(None)),
    regex_line_matcher!(r"convert convert: Unrecognized channel type \((.*)\)\.", |_| Ok(None)),
    regex_line_matcher!(
        r"convert convert: Unable to read font \((.*)\) \[No such file or directory\].",
        file_not_found
    ),
    regex_line_matcher!(
        r"convert convert: Unable to open file (.*) \[No such file or directory\]\.",
        file_not_found
    ),
    regex_line_matcher!(
        r"convert convert: No encode delegate for this image format \((.*)\) \[No such file or directory\].",
        |m| Ok(Some(Box::new(ImageMagickDelegateMissing::new(m.get(1).unwrap().as_str().to_string()))))
    ),
    regex_line_matcher!(r"ERROR: Sphinx requires at least Python (.*) to run.", |_| Ok(None)),
    regex_line_matcher!(r"Can\'t find (.*) directory in (.*)", |_| Ok(None)),
    regex_line_matcher!(
        r"/bin/sh: [0-9]: cannot create (.*): Directory nonexistent",
        |m|  Ok(Some(Box::new(DirectoryNonExistant(std::path::Path::new(m.get(1).unwrap().as_str()).to_path_buf().parent().unwrap().display().to_string()))))
    ),
    regex_line_matcher!(r"dh: Unknown sequence (.*) \(choose from: .*\)", |_| Ok(None)),
    regex_line_matcher!(r".*\.vala:[0-9]+\.[0-9]+-[0-9]+.[0-9]+: error: (.*)", |_| Ok(None)),
    regex_line_matcher!(
        r"error: Package `(.*)\' not found in specified Vala API directories or GObject-Introspection GIR directories",
        |m| Ok(Some(Box::new(MissingValaPackage(m.get(1).unwrap().as_str().to_string()))))
    ),
    regex_line_matcher!(r".*.scala:[0-9]+: error: (.*)", |_| Ok(None)),
    // JavaScript
    regex_line_matcher!(r"error TS6053: File \'(.*)\' not found.", file_not_found),
    // Mocha
    regex_line_matcher!(r"Error \[ERR_MODULE_NOT_FOUND\]: Cannot find package '(.*)' imported from (.*)", |m| Ok(Some(Box::new(MissingNodePackage(m.get(1).unwrap().as_str().to_string()))))),
    regex_line_matcher!(r"\s*Uncaught Error \[ERR_MODULE_NOT_FOUND\]: Cannot find package '(.*)' imported from (.*)",
    |m| Ok(Some(Box::new(MissingNodePackage(m.get(1).unwrap().as_str().to_string()))))),
    regex_line_matcher!(r"(.*\.ts)\([0-9]+,[0-9]+\): error TS[0-9]+: (.*)", |_| Ok(None)),
    regex_line_matcher!(r"(.*.nim)\([0-9]+, [0-9]+\) Error: .*", |_| Ok(None)),
    regex_line_matcher!(
        r"dh_installinit: upstart jobs are no longer supported\!  Please remove (.*) and check if you need to add a conffile removal",
        |m| Ok(Some(Box::new(UpstartFilePresent(m.get(1).unwrap().as_str().to_string()))))
    ),
    regex_line_matcher!(
        r"dh_installinit: --no-restart-on-upgrade has been renamed to --no-stop-on-upgrade",
        |_| Ok(None)
    ),
    regex_line_matcher!(r"find: paths must precede expression: .*", |_| Ok(None)),
    regex_line_matcher!(r"find: ‘(.*)’: No such file or directory", file_not_found),
    regex_line_matcher!(r"ninja: fatal: posix_spawn: Argument list too long", |_| Ok(None)),
    regex_line_matcher!("ninja: fatal: chdir to '(.*)' - No such file or directory", |m| Ok(Some(Box::new(DirectoryNonExistant(m.get(1).unwrap().as_str().to_string()))))),
    // Java
    regex_line_matcher!(r"error: Source option [0-9] is no longer supported. Use [0-9] or later.", |_| Ok(None)),
    regex_line_matcher!(
        r"(dh.*|jh_build): -s/--same-arch has been removed; please use -a/--arch instead",
        |_| Ok(None)
    ),
    regex_line_matcher!(
        r"dh_systemd_start: dh_systemd_start is no longer used in compat >= 11, please use dh_installsystemd instead",
        |_| Ok(None)
    ),
    regex_line_matcher!(r"Trying patch (.*) at level 1 \.\.\. 0 \.\.\. 2 \.\.\. failure.", |_| Ok(None)),
    // QMake
    regex_line_matcher!(r"Project ERROR: (.*) development package not found", pkg_config_missing),
    regex_line_matcher!(r"Package \'(.*)\', required by \'(.*)\', not found\n", pkg_config_missing),
    regex_line_matcher!(r"pkg-config cannot find (.*)", pkg_config_missing),
    regex_line_matcher!(
        r"configure: error: .* not found: Package dependency requirement \'([^\']+)\' could not be satisfied.",
        pkg_config_missing
    ),
    regex_line_matcher!(
        r"configure: error: (.*) is required to build documentation",
        |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))
    ),
    regex_line_matcher!(r".*:[0-9]+: (.*) does not exist.", file_not_found),
    // uglifyjs
    regex_line_matcher!(r"ERROR: can\'t read file: (.*)", file_not_found),
    regex_line_matcher!(r#"jh_build: Cannot find \(any matches for\) "(.*)" \(tried in .*\)"#, |_| Ok(None)),
    regex_line_matcher!(
        r"--   Package \'(.*)\', required by \'(.*)\', not found",
        |m| Ok(Some(Box::new(MissingPkgConfig::simple(m.get(1).unwrap().as_str().to_string()))))
    ),
    regex_line_matcher!(
        r".*.rb:[0-9]+:in `require_relative\': cannot load such file -- (.*) \(LoadError\)",
        |_| Ok(None)
    ),
    regex_line_matcher!(
        r"<internal:.*>:[0-9]+:in `require': cannot load such file -- (.*) \(LoadError\)",
        |m| Ok(Some(Box::new(MissingRubyFile::new(m.get(1).unwrap().as_str().to_string()))))
    ),
    regex_line_matcher!(
        r".*.rb:[0-9]+:in `require\': cannot load such file -- (.*) \(LoadError\)",
        |m| Ok(Some(Box::new(MissingRubyFile::new(m.get(1).unwrap().as_str().to_string()))))
    ),
    regex_line_matcher!(r"LoadError: cannot load such file -- (.*)", |m| Ok(Some(Box::new(MissingRubyFile::new(m.get(1).unwrap().as_str().to_string()))))),
    regex_line_matcher!(r"  cannot load such file -- (.*)", |m| Ok(Some(Box::new(MissingRubyFile::new(m.get(1).unwrap().as_str().to_string()))))),
    // TODO(jelmer): This is a fairly generic string; perhaps combine with other checks for ruby?
    regex_line_matcher!(r"File does not exist: ([a-z/]+)$", |m| Ok(Some(Box::new(MissingRubyFile::new(m.get(1).unwrap().as_str().to_string()))))),
    regex_line_matcher!(
        r".*:[0-9]+:in `do_check_dependencies\': E: dependency resolution check requested but no working gemspec available \(RuntimeError\)",
        |_| Ok(None)
    ),
    regex_line_matcher!(r"rm: cannot remove \'(.*)\': Is a directory", |_| Ok(None)),
    regex_line_matcher!(r"rm: cannot remove \'(.*)\': No such file or directory", file_not_found),
    // Invalid option from Python
    regex_line_matcher!(r"error: option .* not recognized", |_| Ok(None)),
    // Invalid option from go
    regex_line_matcher!(r"flag provided but not defined: .*", |_| Ok(None)),
    regex_line_matcher!(r#"CMake Error: The source directory "(.*)" does not exist."#, |m| Ok(Some(Box::new(DirectoryNonExistant(m.get(1).unwrap().as_str().to_string()))))),
    regex_line_matcher!(r".*: [0-9]+: cd: can\'t cd to (.*)", |m| Ok(Some(Box::new(DirectoryNonExistant(m.get(1).unwrap().as_str().to_string()))))),
    regex_line_matcher!(r"/bin/sh: 0: Can\'t open (.*)", |m| file_not_found_maybe_executable(m.get(1).unwrap().as_str())),
    regex_line_matcher!(r"/bin/sh: [0-9]+: cannot open (.*): No such file", |m| file_not_found_maybe_executable(m.get(1).unwrap().as_str())),
    regex_line_matcher!(r".*: line [0-9]+: (.*): No such file or directory", |m| file_not_found_maybe_executable(m.get(1).unwrap().as_str())),
    regex_line_matcher!(r"/bin/sh: [0-9]+: Syntax error: .*", |_| Ok(None)),
    regex_line_matcher!(r"error: No member named \$memberName", |_| Ok(None)),
    regex_line_matcher!(
        r"(?:/usr/bin/)?install: cannot create regular file \'(.*)\': Permission denied",
        |_| Ok(None)
    ),
    regex_line_matcher!(r"(?:/usr/bin/)?install: cannot create directory .(.*).: File exists", |_| Ok(None)),
    regex_line_matcher!(r"/usr/bin/install: missing destination file operand after .*", |_| Ok(None)),
    // Ruby
    regex_line_matcher!(r"rspec .*\.rb:[0-9]+ # (.*)", |_| Ok(None)),
    // help2man
    regex_line_matcher!(r"Addendum (.*) does NOT apply to (.*) \(translation discarded\).", |_| Ok(None)),
    regex_line_matcher!(
        r"dh_installchangelogs: copy\((.*), (.*)\): No such file or directory",
        file_not_found
    ),
    regex_line_matcher!(r"dh_installman: mv (.*) (.*): No such file or directory", file_not_found),
    regex_line_matcher!(r"dh_installman: Could not determine section for (.*)", |_| Ok(None)),
    regex_line_matcher!(
        r"failed to initialize build cache at (.*): mkdir (.*): permission denied",
        |_| Ok(None)
    ),
    regex_line_matcher!(
        r#"Can't exec "(.*)": No such file or directory at (.*) line ([0-9]+)."#,
        command_missing
    ),
    regex_line_matcher!(
        r#"E OSError: No command "(.*)" found on host .*"#,
        command_missing
    ),
    // PHPUnit
    regex_line_matcher!(r#"Cannot open file "(.*)"."#, file_not_found),
    regex_line_matcher!(
        r".*Could not find a JavaScript runtime\. See https://github.com/rails/execjs for a list of available runtimes\..*",
        |_| Ok(Some(Box::new(MissingJavaScriptRuntime)))
    ),
    Box::new(PythonFileNotFoundErrorMatcher),
    // ruby
    regex_line_matcher!(r"Errno::ENOENT: No such file or directory - (.*)", file_not_found),
    regex_line_matcher!(r"(.*.rb):[0-9]+:in `.*\': .* \(.*\) ", |_| Ok(None)),
    // JavaScript
    regex_line_matcher!(r".*: ENOENT: no such file or directory, open \'(.*)\'", file_not_found),
    regex_line_matcher!(r"\[Error: ENOENT: no such file or directory, stat \'(.*)\'\] \{", file_not_found),
    regex_line_matcher!(
        r"(.*):[0-9]+: error: Libtool library used but \'LIBTOOL\' is undefined",
        |_| Ok(Some(Box::new(MissingLibtool)))
    ),
    // libtoolize
    regex_line_matcher!(r"libtoolize:   error: \'(.*)\' does not exist.", file_not_found),
    // Seen in python-cogent
    regex_line_matcher!(
        "(OSError|RuntimeError): (.*) required but not found.",
        |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(2).unwrap().as_str()))))
    ),
    regex_line_matcher!(
        r"RuntimeError: The (.*) executable cannot be found\. Please check if it is in the system path\.",
        |m| Ok(Some(Box::new(MissingCommand(m.get(1).unwrap().as_str().to_lowercase()))))
    ),
    regex_line_matcher!(
        r".*: [0-9]+: cannot open (.*): No such file",
        file_not_found
    ),
    regex_line_matcher!(
        r"Cannot find Git. Git is required for .*",
        |_| Ok(Some(Box::new(MissingCommand("git".to_string()))))
    ),
    regex_line_matcher!(
        r"E ImportError: Bad (.*) executable\.",
        |m| Ok(Some(Box::new(MissingCommand(m.get(1).unwrap().as_str().to_string()))))
    ),
    regex_line_matcher!(
        "RuntimeError: (.*) is missing",
        |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))
    ),
    regex_line_matcher!(
        r"(OSError|RuntimeError): Could not find (.*) library\..*",
        |m| Ok(Some(Box::new(MissingLibrary(m.get(2).unwrap().as_str().to_string()))))
    ),
    regex_line_matcher!(
        r"(OSError|RuntimeError): We need package (.*), but not importable",
        |m| Ok(Some(Box::new(MissingPythonDistribution{ distribution: m.get(2).unwrap().as_str().to_string(), minimum_version: None, python_version: None })))
    ),
    regex_line_matcher!(
        r"(OSError|RuntimeError): No (.*) was found: .*",
        |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(2).unwrap().as_str()))))
    ),

    regex_line_matcher!(
        r"(.*)meson.build:[0-9]+:[0-9]+: ERROR: Meson version is (.+) but project requires >=\s*(.+)",
        |m| Ok(Some(Box::new(MissingVagueDependency{
            name: "meson".to_string(), url: None,
            minimum_version: Some(m.get(3).unwrap().as_str().trim_end_matches('.').to_string()),
            current_version: Some(m.get(2).unwrap().as_str().to_string())}
        )))
    ),

    // Seen in cpl-plugin-giraf
    regex_line_matcher!(
        r"ImportError: Numpy version (.*) or later must be installed to use .*",
        |m| Ok(Some(Box::new(MissingPythonModule{ module: "numpy".to_string(), python_version: None, minimum_version: Some(m.get(1).unwrap().as_str().to_string())})))
    ),
    // Seen in mayavi2
    regex_line_matcher!(r"\w+Numpy is required to build.*", |_| Ok(Some(Box::new(MissingPythonModule::simple("numpy".to_string()))))),
    // autoconf
    regex_line_matcher!(r"configure.ac:[0-9]+: error: required file \'(.*)\' not found", file_not_found),
    regex_line_matcher!(r"/usr/bin/m4:(.*):([0-9]+): cannot open `(.*)\': No such file or directory", |m| Ok(Some(Box::new(MissingFile{path: std::path::PathBuf::from(m.get(3).unwrap().as_str().to_string())})))),
    // automake
    regex_line_matcher!(r"Makefile.am: error: required file \'(.*)\' not found", file_not_found),
    // sphinx
    regex_line_matcher!(r"config directory doesn\'t contain a conf.py file \((.*)\)", |_| Ok(None)),
    // vcversioner
    regex_line_matcher!(
        r"vcversioner: no VCS could be detected in \'/<<PKGBUILDDIR>>\' and \'/<<PKGBUILDDIR>>/version.txt\' isn\'t present.",
        |_| Ok(None)
    ),
    // rst2html (and other Python?)
    regex_line_matcher!(r"  InputError: \[Errno 2\] No such file or directory: \'(.*)\'", file_not_found),
    // gpg
    regex_line_matcher!(r"gpg: can\'t connect to the agent: File name too long", |_| Ok(None)),
    regex_line_matcher!(r"(.*.lua):[0-9]+: assertion failed", |_| Ok(None)),
    regex_line_matcher!(r"\s+\^\-\-\-\-\^ SC[0-4][0-9][0-9][0-9]: .*", |_| Ok(None)),
    regex_line_matcher!(
        r"Error: (.*) needs updating from (.*)\. Run \'pg_buildext updatecontrol\'.",
        |m| Ok(Some(Box::new(NeedPgBuildExtUpdateControl::new(m.get(1).unwrap().as_str().to_string(), m.get(2).unwrap().as_str().to_string()))))
    ),
    regex_line_matcher!(r"Patch (.*) does not apply \(enforce with -f\)", |m| Ok(Some(Box::new(PatchApplicationFailed::new(m.get(1).unwrap().as_str().to_string()))))),
    regex_line_matcher!(
        r"java.io.FileNotFoundException: ([^ ]+) \(No such file or directory\)",
        file_not_found
    ),
    // Pytest
    regex_line_matcher!(r"INTERNALERROR> PluginValidationError: (.*)", |_| Ok(None)),
    regex_line_matcher!(r"[0-9]+ out of [0-9]+ hunks FAILED -- saving rejects to file (.*\.rej)", |_| Ok(None)),
    regex_line_matcher!(r"pkg_resources.UnknownExtra: (.*) has no such extra feature \'(.*)\'", |_| Ok(None)),
    regex_line_matcher!(
        r"dh_auto_configure: invalid or non-existing path to the source directory: .*",
        |_| Ok(None)
    ),
    // Sphinx
    regex_line_matcher!(
        r"(.*) is no longer a hard dependency since version (.*). Please install it manually.\(pip install (.*)\)",
        |m| Ok(Some(Box::new(MissingPythonModule::simple(m.get(1).unwrap().as_str().to_string()))))
    ),
    regex_line_matcher!(r"There is a syntax error in your configuration file: (.*)", |_| Ok(None)),
    regex_line_matcher!(
        r"E: The Debian version (.*) cannot be used as an ELPA version.",
        |m| Ok(Some(Box::new(DebianVersionRejected::new(m.get(1).unwrap().as_str().to_string()))))
    ),
    regex_line_matcher!(r#""(.*)" is not exported by the ExtUtils::MakeMaker module"#, |_| Ok(None)),
    regex_line_matcher!(
        r"E: Please add appropriate interpreter package to Build-Depends, see pybuild\(1\) for details\..*",
        |_| Ok(Some(Box::new(DhAddonLoadFailure::new("pybuild".to_string(), "Debian/Debhelper/Buildsystem/pybuild.pm".to_string()))))
    ),
    regex_line_matcher!(r"dpkg: error: .*: No space left on device", |_| Ok(Some(Box::new(NoSpaceOnDevice)))),
    regex_line_matcher!(
        r"You need the GNU readline library\(ftp://ftp.gnu.org/gnu/readline/\s+\) to build",
        |_| Ok(Some(Box::new(MissingLibrary("readline".to_string()))))
    ),
    regex_line_matcher!(
        r"configure: error: Could not find lib(.*)",
        |m| Ok(Some(Box::new(MissingLibrary(m.get(1).unwrap().as_str().to_string()))))
    ),
    regex_line_matcher!(
        r"    Could not find module ‘(.*)’",
        |m| Ok(Some(Box::new(MissingHaskellModule::new(m.get(1).unwrap().as_str().to_string()))))
    ),
    regex_line_matcher!(r"E: session: (.*): Chroot not found", |m| Ok(Some(Box::new(ChrootNotFound::new(m.get(1).unwrap().as_str().to_string()))))),
    Box::new(HaskellMissingDependencyMatcher),
    Box::new(SetupPyCommandMissingMatcher),
    Box::new(CMakeErrorMatcher),
    regex_line_matcher!
    (
        r"error: failed to select a version for the requirement `(.*)`",
        |m| {
                let (crate_name, requirement) = match m.get(1).unwrap().as_str().split_once(" ") {
                    Some((cratename, requirement)) => (cratename.to_string(), Some(requirement.to_string())),
                    None => (m.get(1).unwrap().as_str().to_string(), None),
                };
                Ok(Some(Box::new(MissingCargoCrate {
                    crate_name,
                    requirement,
                }))
            )
        }
    ),
    regex_line_matcher!(r"^Environment variable \$SOURCE_DATE_EPOCH: No digits were found: $"),
    regex_line_matcher!(
        r"\[ERROR\] LazyFont - Failed to read font file (.*) \<java.io.FileNotFoundException: (.*) \(No such file or directory\)\>java.io.FileNotFoundException: (.*) \(No such file or directory\)",
        |m| Ok(Some(Box::new(MissingFile::new(m.get(1).unwrap().as_str().into()))))
    ),
    regex_line_matcher!(r"qt.qpa.xcb: could not connect to display", |_m| Ok(Some(Box::new(MissingXDisplay)))),
    regex_line_matcher!(
        r"\(.*:[0-9]+\): Gtk-WARNING \*\*: [0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{3}: cannot open display: ",
        |_m| Ok(Some(Box::new(MissingXDisplay)))
    ),
    regex_line_matcher!(
        r"\s*Package (.*) was not found in the pkg-config search path.",
        |m| Ok(Some(Box::new(MissingPkgConfig::simple(m.get(1).unwrap().as_str().to_string()))))
    ),
    regex_line_matcher!(
        r"Can't open display",
        |_m| Ok(Some(Box::new(MissingXDisplay)))
    ),
    regex_line_matcher!(
        r"Can't open (.+): No such file or directory.*",
        file_not_found
    ),
    regex_line_matcher!(
        r"pkg-config does not know (.*) at .*\.",
        |m| Ok(Some(Box::new(MissingPkgConfig::simple(m.get(1).unwrap().as_str().to_string()))))
    ),
    regex_line_matcher!(
        r"\*\*\* Please install (.*) \(atleast version (.*)\) or adjust",
        |m| Ok(Some(Box::new(MissingPkgConfig{
            module: m.get(1).unwrap().as_str().to_string(),
            minimum_version: Some(m.get(2).unwrap().as_str().to_string())
        })))
    ),
    regex_line_matcher!(
        r"go runtime is required: https://golang.org/doc/install",
        |_m| Ok(Some(Box::new(MissingGoRuntime)))
    ),
    regex_line_matcher!(
        r"\%Error: '(.*)' must be installed to build",
        |m| Ok(Some(Box::new(MissingCommand(m.get(1).unwrap().as_str().to_string()))))
    ),
    regex_line_matcher!(
        r#"configure: error: "Could not find (.*) in PATH"#,
        |m| Ok(Some(Box::new(MissingCommand(m.get(1).unwrap().as_str().to_string()))))
    ),
    regex_line_matcher!(r"Could not find executable (.*)", |m| Ok(Some(Box::new(MissingCommand(m.get(1).unwrap().as_str().to_string()))))),
    regex_line_matcher!(
        r#"go: .*: Get \"(.*)\": x509: certificate signed by unknown authority"#,
        |m| Ok(Some(Box::new(UnknownCertificateAuthority(m.get(1).unwrap().as_str().to_string()))))
    ),
    regex_line_matcher!(
        r#".*.go:[0-9]+:[0-9]+: .*: Get \"(.*)\": x509: certificate signed by unknown authority"#,
        |m| Ok(Some(Box::new(UnknownCertificateAuthority(m.get(1).unwrap().as_str().to_string()))))
    ),
    regex_line_matcher!(
        r"fatal: unable to access '(.*)': server certificate verification failed. CAfile: none CRLfile: none",
        |m| Ok(Some(Box::new(UnknownCertificateAuthority(m.get(1).unwrap().as_str().to_string()))))
    ),
    regex_line_matcher!(
        r"curl: \(77\) error setting certificate verify locations:  CAfile: (.*) CApath: (.*)",
        |m| Ok(Some(Box::new(MissingFile::new(m.get(1).unwrap().as_str().to_string().into()))))
    ),
    regex_line_matcher!(
        r"\t\(Do you need to predeclare (.*)\?\)",
        |m| Ok(Some(Box::new(MissingPerlPredeclared(m.get(1).unwrap().as_str().to_string()))))
    ),
    regex_line_matcher!(
        r#"Bareword \"(.*)\" not allowed while \"strict subs\" in use at Makefile.PL line ([0-9]+)."#,
        |m| Ok(Some(Box::new(MissingPerlPredeclared(m.get(1).unwrap().as_str().to_string()))))
    ),
    regex_line_matcher!(
        r#"String found where operator expected at Makefile.PL line ([0-9]+), near "([a-z0-9_]+).*""#,
        |m| Ok(Some(Box::new(MissingPerlPredeclared(m.get(2).unwrap().as_str().to_string()))))
    ),
    regex_line_matcher!(r"  vignette builder 'knitr' not found", |_| Ok(Some(Box::new(MissingRPackage::simple("knitr"))))),
    regex_line_matcher!(
        r"fatal: unable to auto-detect email address \(got \'.*\'\)",
        |_m| Ok(Some(Box::new(MissingGitIdentity)))
    ),
    regex_line_matcher!(
        r"E       fatal: unable to auto-detect email address \(got \'.*\'\)",
        |_m| Ok(Some(Box::new(MissingGitIdentity)))
    ),
    regex_line_matcher!(r"gpg: no default secret key: No secret key", |_m| Ok(Some(Box::new(MissingSecretGpgKey)))),
    regex_line_matcher!(
        r"ERROR: FAILED--Further testing stopped: Test requires module \'(.*)\' but it\'s not found",
        |m| Ok(Some(Box::new(MissingPerlModule::simple(m.get(1).unwrap().as_str()))))
    ),
    regex_line_matcher!(
        r#"(subprocess.CalledProcessError|error): Command \'\[\'/usr/bin/python([0-9.]*)\', \'-m\', \'pip\', \'--disable-pip-version-check\', \'wheel\', \'--no-deps\', \'-w\', .*, \'([^-][^\']+)\'\]\' returned non-zero exit status 1."#,
        |m| {
            let python_version = m.get(2).filter(|x| !x.is_empty()).map(|pv| pv.as_str().split_once('.').map_or(pv.as_str(), |x| x.0).parse().unwrap());
            Ok(Some(Box::new(MissingPythonDistribution::from_requirement_str(
                m.get(3).unwrap().as_str(), python_version
            ))))
        }
    ),
    regex_line_matcher!(
        r"vcversioner: \[\'git\', .*, \'describe\', \'--tags\', \'--long\'\] failed and \'(.*)/version.txt\' isn\'t present\.",
        |_m| Ok(Some(Box::new(MissingVcVersionerVersion)))
    ),
    regex_line_matcher!(
        r"vcversioner: no VCS could be detected in '(.*)' and '(.*)/version.txt' isn't present\.",
        |_m| Ok(Some(Box::new(MissingVcVersionerVersion)))
    ),
    regex_line_matcher!(
        r"You don't have a working TeX binary \(tex\) installed anywhere in",
        |_m| Ok(Some(Box::new(MissingCommand("tex".to_string()))))
    ),
    regex_line_matcher!(
        r"# Module \'(.*)\' is not installed",
        |m| Ok(Some(Box::new(MissingPerlModule::simple(m.get(1).unwrap().as_str()))))
    ),
    regex_line_matcher!(
        r#"Base class package "(.*)" is empty."#,
        |m| Ok(Some(Box::new(MissingPerlModule::simple(m.get(1).unwrap().as_str()))))
    ),
    regex_line_matcher!(
        r"    \!  (.*::.*) is not installed",
        |m| Ok(Some(Box::new(MissingPerlModule::simple(m.get(1).unwrap().as_str()))))
    ),
    regex_line_matcher!(
        r"Cannot find (.*) in @INC at (.*) line ([0-9]+)\.",
        |m| Ok(Some(Box::new(MissingPerlModule::simple(m.get(1).unwrap().as_str()))))
    ),
    regex_line_matcher!(
        r"(.*::.*) (.*) is required to configure our .* dependency, please install it manually or upgrade your CPAN/CPANPLUS",
        |m| Ok(Some(Box::new(MissingPerlModule::simple(m.get(1).unwrap().as_str()))))
    ),
    regex_line_matcher!(
        r"configure: error: Missing lib(.*)\.",
        |m| Ok(Some(Box::new(MissingLibrary(m.get(1).unwrap().as_str().to_string()))))
    ),
    regex_line_matcher!(
        r"OSError: (.*): cannot open shared object file: No such file or directory",
        |m| Ok(Some(Box::new(MissingFile::new(m.get(1).unwrap().as_str().into()))))
    ),
    regex_line_matcher!(
        r#"The "(.*)" executable has not been found\."#,
        |m| Ok(Some(Box::new(MissingCommand(m.get(1).unwrap().as_str().to_string()))))
    ),
    regex_line_matcher!(
        r"  '\! LaTeX Error: File `(.*)' not found.'",
        |m| Ok(Some(Box::new(MissingLatexFile(m.get(1).unwrap().as_str().to_string()))))
    ),
    regex_line_matcher!(
        r"\! LaTeX Error: File `(.*)\' not found\.",
        |m| Ok(Some(Box::new(MissingLatexFile(m.get(1).unwrap().as_str().to_string()))))
    ),
    regex_line_matcher!(
        r#"(\!|.*:[0-9]+:) Package fontspec Error: The font \"(.*)\" cannot be found\."#,
        |m| Ok(Some(Box::new(MissingFontspec(m.get(2).unwrap().as_str().to_string()))))
    ),
    regex_line_matcher!(r"  vignette builder \'(.*)\' not found", |m| Ok(Some(Box::new(MissingRPackage::simple(m.get(1).unwrap().as_str()))))),
    regex_line_matcher!(
        r"Error: package [‘'](.*)[’'] (.*) was found, but >= (.*) is required by [‘'](.*)[’']",
        |m| Ok(Some(Box::new(MissingRPackage {
            package: m.get(1).unwrap().as_str().to_string(),
            minimum_version: Some(m.get(3).unwrap().as_str().to_string()),
        })))
    ),
    regex_line_matcher!(r"\s*there is no package called \'(.*)\'", |m| Ok(Some(Box::new(MissingRPackage::simple(m.get(1).unwrap().as_str()))))),
    regex_line_matcher!(
        r"Error in .*: there is no package called ‘(.*)’",
        |m| Ok(Some(Box::new(MissingRPackage::simple(m.get(1).unwrap().as_str()))))
    ),
    regex_line_matcher!(
        r"Exception: cannot execute command due to missing interpreter: (.*)",
        command_missing
    ),
    regex_line_matcher!(
        r"E: Build killed with signal TERM after ([0-9]+) minutes of inactivity",
        |m| Ok(Some(Box::new(InactiveKilled(m.get(1).unwrap().as_str().parse().unwrap()))))
    ),
    regex_line_matcher!(
        r#"\[.*Authority\] PAUSE credentials not found in "config.ini" or "dist.ini" or "~/.pause"\! Please set it or specify an authority for this plugin. at inline delegation in Dist::Zilla::Plugin::Authority for logger->log_fatal \(attribute declared in /usr/share/perl5/Dist/Zilla/Role/Plugin.pm at line [0-9]+\) line [0-9]+\."#, |_m| Ok(Some(Box::new(MissingPauseCredentials)))
    ),
    regex_line_matcher!(
        r"npm ERR\! ERROR: \[Errno 2\] No such file or directory: \'(.*)\'",
        file_not_found
    ),
    regex_line_matcher!(
        r"\*\*\* error: gettext infrastructure mismatch: using a Makefile\.in\.in from gettext version ([0-9.]+) but the autoconf macros are from gettext version ([0-9.]+)",
        |m| Ok(Some(Box::new(MismatchGettextVersions{
            makefile_version: m.get(1).unwrap().as_str().to_string(),
            autoconf_version: m.get(2).unwrap().as_str().to_string(),
        })))
    ),
    regex_line_matcher!(
        r"You need to install the (.*) package to use this program\.",
        |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))
    ),
    regex_line_matcher!(r"You need to install (.*)", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))),
    regex_line_matcher!(
        r"configure: error: You don't seem to have the (.*) library installed\..*",
        |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))
    ),
    regex_line_matcher!(
        r"configure: error: You need (.*) installed",
        |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))
    ),
    regex_line_matcher!(
        r"open3: exec of cme (.*) failed: No such file or directory at .*/Dist/Zilla/Plugin/Run/Role/Runner.pm line [0-9]+\.",
        |m| Ok(Some(Box::new(MissingPerlModule::simple(&format!("App::Cme::Command::{}", m.get(1).unwrap().as_str())))))
    ),
    regex_line_matcher!(
        r"pg_ctl: cannot be run as (.*)",
        |m| Ok(Some(Box::new(InvalidCurrentUser(m.get(1).unwrap().as_str().to_string()))))
    ),
    regex_line_matcher!(
        r"([^ ]+) \(for section ([^ ]+)\) does not appear to be installed",
        |m| Ok(Some(Box::new(MissingPerlModule::simple(m.get(1).unwrap().as_str()))))
    ),
    regex_line_matcher!(
        r"(.*) version (.*) required--this is only version (.*) at .*\.pm line [0-9]+\.",
        |m| Ok(Some(Box::new(MissingPerlModule {
            module: m.get(1).unwrap().as_str().to_string(),
            minimum_version: Some(m.get(2).unwrap().as_str().to_string()),
            inc: None,
            filename: None,
        })))
    ),
    regex_line_matcher!(
        r"Bailout called\.  Further testing stopped:  YOU ARE MISSING REQUIRED MODULES: \[ ([^,]+)(.*) \]:",
        |m| Ok(Some(Box::new(MissingPerlModule::simple(m.get(1).unwrap().as_str()))))
    ),
    regex_line_matcher!(
        r#"CMake Error: CMake was unable to find a build program corresponding to "(.*)".  CMAKE_MAKE_PROGRAM is not set\.  You probably need to select a different build tool\."#,
        |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))
    ),
    regex_line_matcher!(
        r"Dist currently only works with Git or Mercurial repos",
        |_| Ok(Some(Box::new(VcsControlDirectoryNeeded::new(vec!["git", "hg"]))))
    ),
    regex_line_matcher!(
        r"GitHubMeta: need a .git\/config file, and you don\'t have one",
        |_| Ok(Some(Box::new(VcsControlDirectoryNeeded::new(vec!["git"]))))
    ),
    regex_line_matcher!(
        r"Exception: Versioning for this project requires either an sdist tarball, or access to an upstream git repository\. It's also possible that there is a mismatch between the package name in setup.cfg and the argument given to pbr\.version\.VersionInfo\. Project name .* was given, but was not able to be found\.",
        |_| Ok(Some(Box::new(VcsControlDirectoryNeeded::new(vec!["git"]))))
    ),
    regex_line_matcher!(
        r"configure: error: no suitable Python interpreter found",
        |_| Ok(Some(Box::new(MissingCommand("python".to_string()))))
    ),
    regex_line_matcher!(r#"Could not find external command "(.*)""#, |m| Ok(Some(Box::new(MissingCommand(m.get(1).unwrap().as_str().to_string()))))),
    regex_line_matcher!(
        r"  Failed to find (.*) development headers\.",
        |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))
    ),
    regex_line_matcher!(
        r"\*\*\* \Subdirectory \'(.*)\' does not yet exist. Use \'./gitsub.sh pull\' to create it, or set the environment variable GNULIB_SRCDIR\.",
        |m| Ok(Some(Box::new(MissingGnulibDirectory(m.get(1).unwrap().as_str().into()))))
    ),
    regex_line_matcher!(
        r"configure: error: Cap\'n Proto compiler \(capnp\) not found.",
        |_| Ok(Some(Box::new(MissingCommand("capnp".to_string()))))
    ),
    regex_line_matcher!(
        r"lua: (.*):(\d+): module \'(.*)\' not found:",
        |m| Ok(Some(Box::new(MissingLuaModule(m.get(3).unwrap().as_str().to_string()))))
    ),
    regex_line_matcher!(r"Unknown key\(s\) in sphinx_gallery_conf:"),
    regex_line_matcher!(r"(.+\.gir):In (.*): error: (.*)"),
    regex_line_matcher!(r"(.+\.gir):[0-9]+\.[0-9]+-[0-9]+\.[0-9]+: error: (.*)"),
    regex_line_matcher!(r"psql:.*\.sql:[0-9]+: ERROR:  (.*)"),
    regex_line_matcher!(r"intltoolize: \'(.*)\' is out of date: use \'--force\' to overwrite"),
    regex_line_matcher!(
        r"E: pybuild pybuild:[0-9]+: cannot detect build system, please use --system option or set PYBUILD_SYSTEM env\. variable"
    ),
    regex_line_matcher!(
        r"--   Requested \'(.*) >= (.*)\' but version of (.*) is (.*)",
        |m| Ok(Some(Box::new(MissingPkgConfig{
            module: m.get(1).unwrap().as_str().to_string(),
            minimum_version: Some(m.get(2).unwrap().as_str().to_string()),
        })))
    ),
    regex_line_matcher!(
        r".*Could not find (.*) lib/headers, please set .* or ensure (.*).pc is in PKG_CONFIG_PATH\.",
        |m| Ok(Some(Box::new(MissingPkgConfig::simple(m.get(2).unwrap().as_str().to_string()))))
    ),
    regex_line_matcher!(
        r"go: go.mod file not found in current directory or any parent directory; see \'go help modules\'",
        |_| Ok(Some(Box::new(MissingGoModFile)))
    ),
    regex_line_matcher!(
        r"go: cannot find main module, but found Gopkg.lock in (.*)",
        |_| Ok(Some(Box::new(MissingGoModFile)))
    ),
    regex_line_matcher!(r"go: updates to go.mod needed; to update it:", |_| Ok(Some(Box::new(OutdatedGoModFile)))),
    regex_line_matcher!(r"(c\+\+|collect2|cc1|g\+\+): fatal error: .*"),
    regex_line_matcher!(r"fatal: making (.*): failed to create tests\/decode.trs"),
    // ocaml
    regex_line_matcher!(r"Please specify at most one of .*"),
    // Python lint
    regex_line_matcher!(r".*\.py:[0-9]+:[0-9]+: [A-Z][0-9][0-9][0-9] .*"),
    regex_line_matcher!(
        r#"PHPUnit requires the "(.*)" extension\."#,
        |m| Ok(Some(Box::new(MissingPHPExtension(m.get(1).unwrap().as_str().to_string()))))
    ),
    regex_line_matcher!(
        r#"     \[exec\] PHPUnit requires the "(.*)" extension\."#,
        |m| Ok(Some(Box::new(MissingPHPExtension(m.get(1).unwrap().as_str().to_string()))))
    ),
    regex_line_matcher!(
        r".*/gnulib-tool: \*\*\* minimum supported autoconf version is (.*)\. ",
        |m| Ok(Some(Box::new(MinimumAutoconfTooOld(m.get(1).unwrap().as_str().to_string()))))
    ),
    regex_line_matcher!(
        r"configure.(ac|in):[0-9]+: error: Autoconf version (.*) or higher is required",
        |m| Ok(Some(Box::new(MissingVagueDependency {
            name: "autoconf".to_string(),
            url: None,
            minimum_version: Some(m.get(2).unwrap().as_str().to_string()),
            current_version: None,
        })))
    ),
    regex_line_matcher!(
        r#"# Error: The file "(MANIFEST|META.yml)" is missing from this distribution\\. .*"#,
        |m| Ok(Some(Box::new(MissingPerlDistributionFile(m.get(1).unwrap().as_str().to_string()))))
    ),
    regex_line_matcher!(r"^  ([^ ]+) does not exist$", file_not_found),
    regex_line_matcher!(
        r"\s*> Cannot find \'\.git\' directory",
        |_m| Ok(Some(Box::new(VcsControlDirectoryNeeded::new(vec!["git"]))))
    ),
    regex_line_matcher!(
        r"Unable to find the \'(.*)\' executable\. .*",
        |m| Ok(Some(Box::new(MissingCommand(m.get(1).unwrap().as_str().to_string()))))
    ),
    regex_line_matcher!(
        r"\[@RSRCHBOY\/CopyrightYearFromGit\]  -  412 No \.git subdirectory found",
        |_m| Ok(Some(Box::new(VcsControlDirectoryNeeded::new(vec!["git"]))))
    ),
    regex_line_matcher!(
        r"Couldn\'t find version control data \(git/hg/bzr/svn supported\)",
        |_m| Ok(Some(Box::new(VcsControlDirectoryNeeded::new(vec!["git", "hg", "bzr", "svn"]))))
    ),
    regex_line_matcher!(
        r"RuntimeError: Unable to determine package version. No local Git clone detected, and no version file found at .*",
        |_m| Ok(Some(Box::new(VcsControlDirectoryNeeded::new(vec!["git"]))))
    ),
    regex_line_matcher!(
        r#""(.*)" failed to start: "No such file or directory" at .*.pm line [0-9]+\."#,
        |m| Ok(Some(Box::new(MissingCommand(m.get(1).unwrap().as_str().to_string()))))
    ),
    regex_line_matcher!(r"Can\'t find ([^ ]+)\.", |m| Ok(Some(Box::new(MissingCommand(m.get(1).unwrap().as_str().to_string()))))),
    regex_line_matcher!(r"Error: spawn (.*) ENOENT", |m| Ok(Some(Box::new(MissingCommand(m.get(1).unwrap().as_str().to_string()))))),
    regex_line_matcher!(
        r"E ImportError: Failed to initialize: Bad (.*) executable\.",
        |m| Ok(Some(Box::new(MissingCommand(m.get(1).unwrap().as_str().to_string()))))
    ),
    regex_line_matcher!(
        r#"ESLint couldn\'t find the config "(.*)" to extend from\. Please check that the name of the config is correct\."#
    ),
    regex_line_matcher!(
        r#"E OSError: no library called "cairo-2" was found"#,
        |m| Ok(Some(Box::new(MissingLibrary(m.get(1).unwrap().as_str().to_string()))))
    ),
    regex_line_matcher!(
        r"ERROR: \[Errno 2\] No such file or directory: '(.*)'",
 |m| file_not_found_maybe_executable(m.get(1).unwrap().as_str())
    ),
    regex_line_matcher!(
        r"error: \[Errno 2\] No such file or directory: '(.*)'",
 |m| file_not_found_maybe_executable(m.get(1).unwrap().as_str())
    ),
    regex_line_matcher!(
        r"We need the Python library (.+) to be installed\. .*",
        |m| Ok(Some(Box::new(MissingPythonDistribution::simple(m.get(1).unwrap().as_str()))))
    ),
    // Waf
    regex_line_matcher!(
        r"Checking for header (.+\.h|.+\.hpp)\s+: not found ",
        |m| Ok(Some(Box::new(MissingCHeader::new(m.get(1).unwrap().as_str().to_string()))))
    ),
    regex_line_matcher!(
        r"000: File does not exist (.*)",
        file_not_found
    ),
    regex_line_matcher!(
        r"ERROR: Coverage for lines \(([0-9.]+)%\) does not meet global threshold \(([0-9]+)%\)",
        |m| Ok(Some(Box::new(CodeCoverageTooLow{
            actual: m.get(1).unwrap().as_str().parse().unwrap(),
            required: m.get(2).unwrap().as_str().parse().unwrap()})))
    ),
    regex_line_matcher!(
        r"Error \[ERR_REQUIRE_ESM\]: Must use import to load ES Module: (.*)",
        |m| Ok(Some(Box::new(ESModuleMustUseImport(m.get(1).unwrap().as_str().to_string()))))
    ),
    regex_line_matcher!(r".* (/<<BUILDDIR>>/.*): No such file or directory", file_not_found),
    regex_line_matcher!(
        r"Cannot open file `(.*)' in mode `(.*)' \(No such file or directory\)",
        file_not_found
    ),
    regex_line_matcher!(r"[^:]+: cannot stat \'(.*)\': No such file or directory", file_not_found),
    regex_line_matcher!(r"cat: (.*): No such file or directory", file_not_found),
    regex_line_matcher!(r"ls: cannot access \'(.*)\': No such file or directory", file_not_found),
    regex_line_matcher!(
        r"Problem opening (.*): No such file or directory at (.*) line ([0-9]+)\.",
        file_not_found
    ),
    regex_line_matcher!(r"/bin/bash: (.*): No such file or directory", file_not_found),
    regex_line_matcher!(
        r#"\(The package "(.*)" was not found when loaded as a Node module from the directory ".*"\.\)"#,
        |m| Ok(Some(Box::new(MissingNodePackage(m.get(1).unwrap().as_str().to_string()))))
    ),
    regex_line_matcher!(r"\+\-\- UNMET DEPENDENCY (.*)", |m| Ok(Some(Box::new(MissingNodePackage(m.get(1).unwrap().as_str().to_string()))))),
    regex_line_matcher!(
        r"Project ERROR: Unknown module\(s\) in QT: (.*)",
        |m| Ok(Some(Box::new(MissingQtModules(m.get(1).unwrap().as_str().split_whitespace().map(|s| s.to_string()).collect()))))
    ),
    regex_line_matcher!(
        r"(.*):(\d+):(\d+): ERROR: Vala compiler \'.*\' can not compile programs",
        |_| Ok(Some(Box::new(ValaCompilerCannotCompile)))
    ),
    regex_line_matcher!(
        r"(.*):(\d+):(\d+): ERROR: Problem encountered: Cannot load ([^ ]+) library\. (.*)",
        |m| Ok(Some(Box::new(MissingLibrary(m.get(4).unwrap().as_str().to_string()))))
    ),
    regex_line_matcher!(
        r"go: (.*)@(.*): missing go.sum entry; to add it:",
        |m| Ok(Some(Box::new(MissingGoSumEntry {
            package: m.get(1).unwrap().as_str().to_string(),
            version: m.get(2).unwrap().as_str().to_string(),
        })))
    ),
    regex_line_matcher!(
        r"E: pybuild pybuild:(.*): configure: plugin (.*) failed with: PEP517 plugin dependencies are not available\. Please Build-Depend on (.*)\.",
        |m| Ok(Some(Box::new(MissingDebianBuildDep(m.get(1).unwrap().as_str().to_string()))))
    ),
    regex_line_matcher!(
        r"^make\[[0-9]+\]: \*\*\* No rule to make target '(.*)', needed by '(.*)'\.  Stop\.$",
        |m| Ok(Some(Box::new(MissingMakeTarget::new(m.get(1).unwrap().as_str(), Some(m.get(2).unwrap().as_str())))))
    ),
    regex_line_matcher!(r#"make: \*\*\* No rule to make target \'(.*)\'\.  Stop\."#, |m| Ok(Some(Box::new(MissingMakeTarget::simple(m.get(1).unwrap().as_str()))))),
    regex_line_matcher!(
        r"make\[[0-9]+\]: \*\*\* No rule to make target \'(.*)\'\.  Stop\.", |m| Ok(Some(Box::new(MissingMakeTarget::simple(m.get(1).unwrap().as_str()))))),
    // ADD NEW REGEXES ABOVE THIS LINE
    regex_line_matcher!(
        r#"configure: error: Can not find "(.*)" .* in your PATH"#,
        |m| Ok(Some(Box::new(MissingCommand(m.get(1).unwrap().as_str().to_string()))))
    ),
    // Intentionally at the bottom of the list.
    regex_line_matcher!(
        r"([^ ]+) package not found\. Please install from (https://[^ ]+)",
        |m| Ok(Some(Box::new(MissingVagueDependency {name:m.get(1).unwrap().as_str().to_string(),url:Some(m.get(2).unwrap().as_str().to_string()), minimum_version: None, current_version: None })))
    ),
    regex_line_matcher!(
        r"([^ ]+) package not found\. Please use \'pip install .*\' first",
        |m| Ok(Some(Box::new(MissingPythonDistribution::simple(m.get(1).unwrap().as_str()))))
    ),
    regex_line_matcher!(r".*: No space left on device", |_m| Ok(Some(Box::new(NoSpaceOnDevice)))),
    regex_line_matcher!(r".*(No space left on device).*", |_m| Ok(Some(Box::new(NoSpaceOnDevice)))),
    regex_line_matcher!(
        r"ocamlfind: Package `(.*)\' not found",
        |m| Ok(Some(Box::new(MissingOCamlPackage(m.get(1).unwrap().as_str().to_string()))))
    ),
    // Not a very unique ocaml-specific pattern :(
    regex_line_matcher!(r#"Error: Library "(.*)" not found."#, |m| Ok(Some(Box::new(MissingOCamlPackage(m.get(1).unwrap().as_str().to_string()))))),
    // ADD NEW REGEXES ABOVE THIS LINE
    // Intentionally at the bottom of the list, since they're quite broad.
    regex_line_matcher!(
        r"configure: error: ([^ ]+) development files not found",
        |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))
    ),
    regex_line_matcher!(
        r"Exception: ([^ ]+) development files not found\..*",
        |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))
    ),
    regex_line_matcher!(
        r"Exception: Couldn\'t find (.*) source libs\!",
        |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))
    ),
    regex_line_matcher!(
        "configure: error: '(.*)' command was not found",
        |m| Ok(Some(Box::new(MissingCommand(m.get(1).unwrap().as_str().to_string()))))
    ),
    regex_line_matcher!(
        r"configure: error: (.*) not present.*",
        |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))
    ),
    regex_line_matcher!(
        r"configure: error: (.*) >= (.*) not found",
        |m| Ok(Some(Box::new(MissingVagueDependency {
            name: m.get(1).unwrap().as_str().to_string(),
            minimum_version: Some(m.get(2).unwrap().as_str().to_string()),
            url: None, current_version: None
        })))
    ),
    regex_line_matcher!(
        r"configure: error: (.*) headers (could )?not (be )?found",
        |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))
    ),
    regex_line_matcher!(
        r"configure: error: (.*) ([0-9].*) (could )?not (be )?found",
        |m| Ok(Some(Box::new(MissingVagueDependency {
            name: m.get(1).unwrap().as_str().to_string(),
            minimum_version: Some(m.get(2).unwrap().as_str().to_string()),
            url: None, current_version: None
        })))
    ),
    regex_line_matcher!(
        r"configure: error: (.*) (could )?not (be )?found",
        |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))
    ),
    regex_line_matcher!(
        r"configure: error: (.*) ([0-9.]+) is required to build.*",
        |m| Ok(Some(Box::new(MissingVagueDependency {name:m.get(1).unwrap().as_str().to_string(),minimum_version:Some(m.get(2).unwrap().as_str().to_string()),url:None, current_version: None })))
    ),
    regex_line_matcher!(
        ".*meson.build:([0-9]+):([0-9]+): ERROR: Problem encountered: (.*) (.*) or later required",
        |m| Ok(Some(Box::new(MissingVagueDependency {
            name: m.get(3).unwrap().as_str().to_string(),
            minimum_version: Some(m.get(4).unwrap().as_str().to_string()),
                url: None, current_version: None
        })))
    ),
    regex_line_matcher!(
        r"configure: error: Please install (.*) from (http:\/\/[^ ]+)",
        |m| Ok(Some(Box::new(MissingVagueDependency {
            name: m.get(1).unwrap().as_str().to_string(),
            url: Some(m.get(2).unwrap().as_str().to_string()),
            minimum_version: None, current_version: None
        })))
    ),
    regex_line_matcher!(
        r"configure: error: Required package (.*) (is ?)not available\.",
        |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))
    ),
    regex_line_matcher!(
        r"Error\! You need to have (.*) \((.*)\) around.",
        |m| Ok(Some(Box::new(MissingVagueDependency {
            name: m.get(1).unwrap().as_str().to_string(),
            minimum_version: Some(m.get(2).unwrap().as_str().to_string()),
            url: None, current_version: None
        })))
    ),
    regex_line_matcher!(
        r"configure: error: You don\'t have (.*) installed",
        |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))
    ),
    regex_line_matcher!(
        r"configure: error: Could not find a recent version of (.*)",
        |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))
    ),
    regex_line_matcher!(
        r"configure: error: Unable to locate (.*)",
        |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))
    ),
    regex_line_matcher!(
        r"configure: error: Missing the (.* library)",
        |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))
    ),
    regex_line_matcher!(
        r"configure: error: (.*) requires (.* libraries), .*",
        |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(2).unwrap().as_str()))))
    ),
    regex_line_matcher!(
        r"configure: error: (.*) requires ([^ ]+)\.",
        |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(2).unwrap().as_str()))))
    ),
    regex_line_matcher!(
        r"(.*) cannot be discovered in ([^ ]+)",
        |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))
    ),
    regex_line_matcher!(
        r"configure: error: Missing required program '(.*)'.*",
        |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))
    ),
    regex_line_matcher!(
        r"configure: error: Missing (.*)\.",
        |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))
    ),
    regex_line_matcher!(
        r"configure: error: Unable to find (.*), please install (.*)",
        |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(2).unwrap().as_str()))))
    ),
    regex_line_matcher!(r"configure: error: (.*) Not found", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))),
    regex_line_matcher!(
        r"configure: error: You need to install (.*)",
        |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))
    ),
    regex_line_matcher!(
        r"configure: error: (.*) \((.*)\) not found\.",
        |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(2).unwrap().as_str()))))
    ),
    regex_line_matcher!(
        r"configure: error: (.*) libraries are required for compilation",
        |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))
    ),
    regex_line_matcher!(
        r"configure: error: .*Make sure you have (.*) installed\.",
        |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))
    ),
    regex_line_matcher!(
        r"error: Cannot find (.*) in the usual places. .*",
        |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))
    ),
    regex_line_matcher!(
        r#"Makefile:[0-9]+: \*\*\* "(.*) was not found"\.  Stop\."#,
        |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))
    ),
    regex_line_matcher!(
        r#"Makefile:[0-9]+: \*\*\* \"At least (.*) version (.*) is needed to build (.*)\.".  Stop\."#,
        |m| Ok(Some(Box::new(MissingVagueDependency {
            name: m.get(1).unwrap().as_str().to_string(),
            minimum_version: Some(m.get(2).unwrap().as_str().to_string()),
            url: None, current_version: None
        })))
    ),
    regex_line_matcher!(r"([a-z0-9A-Z]+) not found", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))),
    regex_line_matcher!(r"ERROR:  Unable to locate (.*)\.", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))),
    regex_line_matcher!(
        "\x1b\\[1;31merror: (.*) not found\x1b\\[0;32m",
        |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))
    ),
    regex_line_matcher!(
        r"You do not have (.*) correctly installed\. .*",
        |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))
    ),
    regex_line_matcher!(
        r"Error: (.*) is not available on your system",
        |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))
    ),
    regex_line_matcher!(
        r"ERROR: (.*) (.*) or later is required",
        |m| Ok(Some(Box::new(MissingVagueDependency {
            name: m.get(1).unwrap().as_str().to_string(),
            minimum_version: Some(m.get(2).unwrap().as_str().to_string()),
            url: None,
            current_version: None
        })))
    ),
    regex_line_matcher!(
        r"configure: error: .*Please install the \'(.*)\' package\.",
        |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))
    ),
    regex_line_matcher!(
        r"Error: Please install ([^ ]+) package",
        |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))
    ),
    regex_line_matcher!(r"configure: error: <(.*\.h)> is required", |m| Ok(Some(Box::new(MissingCHeader::new(m.get(1).unwrap().as_str().to_string()))))),
    regex_line_matcher!(
        r"configure: error: ([^ ]+) is required",
        |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))
    ),
    regex_line_matcher!(
        r"configure: error: you should install ([^ ]+) first",
        |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))
    ),
    regex_line_matcher!(
        r"configure: error: .*You need (.*) installed.",
        |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))
    ),
    regex_line_matcher!(r"To build (.*) you need (.*)", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))),
    regex_line_matcher!(r".*Can\'t ([^\. ]+)\. (.*)", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))),
    regex_line_matcher!(
        r"([^ ]+) >= (.*) is required",
        |m| Ok(Some(Box::new(MissingVagueDependency {
            name: m.get(1).unwrap().as_str().to_string(),
            minimum_version: Some(m.get(1).unwrap().as_str().to_string()),
            current_version: None,
            url: None
        })))
    ),
    regex_line_matcher!(
        r".*: ERROR: (.*) needs to be installed to run these tests",
        |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))
    ),
    regex_line_matcher!(r"ERROR: Unable to locate (.*)\.", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))),
    regex_line_matcher!(
        r"ERROR: Cannot find command \'(.*)\' - do you have \'(.*)\' installed and in your PATH\?",
        |m| Ok(Some(Box::new(MissingCommand(m.get(1).unwrap().as_str().to_string()))))
    ),
    regex_line_matcher!(
        r"ValueError: no ([^ ]+) installed, .*",
        |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))
    ),
    regex_line_matcher!(
        r"This project needs (.*) in order to build\. .*",
        |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))
    ),
    regex_line_matcher!(r"ValueError: Unable to find (.+)", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))),
    regex_line_matcher!(r"([^ ]+) executable not found\. .*", |m| Ok(Some(Box::new(MissingCommand(m.get(1).unwrap().as_str().to_string()))))),
    regex_line_matcher!(
        r"ERROR: InvocationError for command could not find executable (.*)",
        |m| Ok(Some(Box::new(MissingCommand(m.get(1).unwrap().as_str().to_string()))))
    ),
    regex_line_matcher!(
        r"E ImportError: Unable to find ([^ ]+) shared library",
        |m| Ok(Some(Box::new(MissingLibrary(m.get(1).unwrap().as_str().to_string()))))
    ),
    regex_line_matcher!(
        r"\s*([^ ]+) library not found on the system",
        |m| Ok(Some(Box::new(MissingLibrary(m.get(1).unwrap().as_str().to_string()))))
    ),
    regex_line_matcher!(r"\s*([^ ]+) library not found(\.?)", |m| Ok(Some(Box::new(MissingLibrary(m.get(1).unwrap().as_str().to_string()))))),
    regex_line_matcher!(
        r".*Please install ([^ ]+) libraries\.",
        |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))
    ),
    regex_line_matcher!(
        r"Error: Please install (.*) package",
        |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))
    ),
    regex_line_matcher!(
        r"Please get ([^ ]+) from (www\..*)\.",
        |m| Ok(Some(Box::new(MissingVagueDependency {
            name: m.get(1).unwrap().as_str().to_string(),
            url: Some(m.get(2).unwrap().as_str().to_string()),
            minimum_version: None, current_version: None
        })))
    ),
    regex_line_matcher!(
        r"Please install ([^ ]+) so that it is on the PATH and try again\.",
        |m| Ok(Some(Box::new(MissingCommand(m.get(1).unwrap().as_str().to_string()))))
    ),
    regex_line_matcher!(
        r"configure: error: No (.*) binary found in (.*)",
        |m| Ok(Some(Box::new(MissingCommand(m.get(1).unwrap().as_str().to_string()))))
    ),
    regex_line_matcher!(r"Could not find ([A-Za-z-]+)$", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))),
    regex_line_matcher!(
        r"No ([^ ]+) includes and libraries found",
        |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))
    ),
    regex_line_matcher!(
        r"Required library (.*) not found\.",
        |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))
    ),
    regex_line_matcher!(r"Missing ([^ ]+) boost library, .*", |m| Ok(Some(Box::new(MissingLibrary(m.get(1).unwrap().as_str().to_string()))))),
    regex_line_matcher!(
        r"configure: error: ([^ ]+) needed\!",
        |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))
    ),
    regex_line_matcher!(
        r"\*\*\* (.*) not found, please install it \*\*\*",
        |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))
    ),
    regex_line_matcher!(
        r"configure: error: could not find ([^ ]+)",
        |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))
    ),
    regex_line_matcher!(
        r"([^ ]+) is required for ([^ ]+)\.",
        |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))
    ),
    regex_line_matcher!(
        r"configure: error: \*\*\* No ([^.])\! Install (.*) development headers/libraries! \*\*\*",
        |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))
    ),
    regex_line_matcher!(
        r"configure: error: \'(.*)\' cannot be found",
        |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))
    ),
    regex_line_matcher!(
        r"No (.*) includes and libraries found",
        |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))
    ),
    regex_line_matcher!(
        r"\s*No (.*) version could be found in your system\.",
        |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))
    ),
    regex_line_matcher!(r"You need (.+)", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))),
    regex_line_matcher!(
        r"configure: error: ([^ ]+) is needed",
        |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))
    ),
    regex_line_matcher!(
        r"configure: error: Cannot find ([^ ]+)\.",
        |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))
    ),
    regex_line_matcher!(
        r"configure: error: ([^ ]+) requested but not installed\.",
        |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))
    ),
    regex_line_matcher!(
        r"We need the Python library (.+) to be installed\..*",
        |m| Ok(Some(Box::new(MissingPythonDistribution::simple(m.get(1).unwrap().as_str()))))
    ),
    regex_line_matcher!(
        r"(.*) uses (.*) \(.*\) for installation but (.*) was not found",
        |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))
    ),
    regex_line_matcher!(
        r"ERROR: could not locate the \'([^ ]+)\' utility",
        |m| Ok(Some(Box::new(MissingCommand(m.get(1).unwrap().as_str().to_string()))))
    ),
    regex_line_matcher!(r"Can\'t find (.*) libs. Exiting", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))),
    ]);
}

lazy_static::lazy_static! {
    static ref CMAKE_ERROR_MATCHERS: MatcherGroup = MatcherGroup::new(vec![
        regex_para_matcher!(r"Could NOT find (.*) \(missing:\s(.*)\)\s\(found\ssuitable\sversion\s.*",
            |m| Ok(Some(Box::new(MissingCMakeComponents{
                name: m.get(1).unwrap().as_str().to_string(),
                components: m.get(2).unwrap().as_str().split_whitespace().map(|s| s.to_string()).collect()})))
        ),
        regex_para_matcher!(r"\s*--\s+Package \'(.*)\', required by \'(.*)\', not found",
            |m| Ok(Some(Box::new(MissingPkgConfig::simple(m.get(1).unwrap().as_str().to_string()))))
        ),
        regex_para_matcher!(r#"Could not find a package configuration file provided by\s"(.*)" \(requested\sversion\s(.*)\)\swith\sany\s+of\s+the\s+following\snames:\n\n(  .*\n)+\n.*$"#,
            |m| {
                let package = m.get(1).unwrap().as_str().to_string();
                let version = m.get(2).unwrap().as_str().to_string();
                let _names = m.get(3).unwrap().as_str().split_whitespace().map(|s| s.to_string()).collect::<Vec<_>>();
                Ok(Some(Box::new(MissingCMakeConfig{
                    name: package,
                    version: Some(version),
                })))
            }
        ),
        regex_para_matcher!(
            r"Could NOT find (.*) \(missing: (.*)\)",
            |m| {
                let name = m.get(1).unwrap().as_str().to_string();
                let components = m.get(2).unwrap().as_str().split_whitespace().map(|s| s.to_string()).collect();
                Ok(Some(Box::new(MissingCMakeComponents {
                    name,
                    components,
                })))
            }
        ),
        regex_para_matcher!(
            r#"The (.+) compiler\n\n  "(.*)"\n\nis not able to compile a simple test program\.\n\nIt fails with the following output:\n\n(.*)\n\nCMake will not be able to correctly generate this project.\n$"#,
            |m| {
                let compiler_output = textwrap::dedent(m.get(3).unwrap().as_str());
                let (_match, error) = find_build_failure_description(compiler_output.split_inclusive('\n').collect());
                Ok(error)
            }
        ),
        regex_para_matcher!(
            r#"Could NOT find (.*): Found unsuitable version \"(.*)\",\sbut\srequired\sis\sexact version \"(.*)\" \(found\s(.*)\)"#,
            |m| {
                let package = m.get(1).unwrap().as_str().to_string();
                let version_found = m.get(2).unwrap().as_str().to_string();
                let exact_version_needed = m.get(3).unwrap().as_str().to_string();
                let path = m.get(4).unwrap().as_str().to_string();

                Ok(Some(Box::new(CMakeNeedExactVersion {
                    package,
                    version_found,
                    exact_version_needed,
                    path: std::path::PathBuf::from(path),
                })))
            }
        ),
        regex_para_matcher!(
            r"(.*) couldn't be found \(missing: .*_LIBRARIES .*_INCLUDE_DIR\)",
            |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))
        ),
        regex_para_matcher!(
            r#"Could NOT find (.*): Found unsuitable version \"(.*)\",\sbut\srequired\sis\sat\sleast\s\"(.*)\" \(found\s(.*)\)"#,
            |m| Ok(Some(Box::new(MissingPkgConfig{
                module: m.get(1).unwrap().as_str().to_string(),
                minimum_version: Some(m.get(3).unwrap().as_str().to_string())})))
        ),
        regex_para_matcher!(
            r#"The imported target \"(.*)\" references the file\n\n\s*"(.*)"\n\nbut this file does not exist\.(.*)"#,
            |m| Ok(Some(Box::new(MissingFile::new(m.get(2).unwrap().as_str().to_string().into()))))
        ),
        regex_para_matcher!(
            r#"Could not find a configuration file for package "(.*)"\sthat\sis\scompatible\swith\srequested\sversion\s"(.*)"\."#,
            |m| Ok(Some(Box::new(MissingCMakeConfig {
                name: m.get(1).unwrap().as_str().to_string(),
                version: Some(m.get(2).unwrap().as_str().to_string())})))
        ),
        regex_para_matcher!(
            r#".*Could not find a package configuration file provided by "(.*)"\s+with\s+any\s+of\s+the\s+following\s+names:\n\n(  .*\n)+\n.*$"#,
            |m| Ok(Some(Box::new(CMakeFilesMissing{ filenames: m.get(2).unwrap().as_str().split_whitespace().map(|s| s.to_string()).collect(), version: None })))
        ),
        regex_para_matcher!(
            r#".*Could not find a package configuration file provided by "(.*)"\s\(requested\sversion\s(.+\))\swith\sany\sof\sthe\sfollowing\snames:\n\n(  .*\n)+\n.*$"#, |m| {
                let package = m.get(1).unwrap().as_str().to_string();
                let versions = m.get(2).unwrap().as_str().to_string();
                let _names = m.get(3).unwrap().as_str().split_whitespace().map(|s| s.to_string()).collect::<Vec<_>>();
                Ok(Some(Box::new(MissingCMakeConfig {
                    name: package,
                    version: Some(versions),
                })))
            }
        ),
        regex_para_matcher!(
            r#"No CMAKE_(.*)_COMPILER could be found.\n\nTell CMake where to find the compiler by setting either\sthe\senvironment\svariable\s"(.*)"\sor\sthe\sCMake\scache\sentry\sCMAKE_(.*)_COMPILER\sto\sthe\sfull\spath\sto\sthe\scompiler,\sor\sto\sthe\scompiler\sname\sif\sit\sis\sin\sthe\sPATH.\n"#,
            |m| Ok(Some(Box::new(MissingCommand(m.get(1).unwrap().as_str().to_lowercase()))))
        ),
        regex_para_matcher!(r#"file INSTALL cannot find\s"(.*)".\n"#, |m| Ok(Some(Box::new(MissingFile::new(m.get(1).unwrap().as_str().into()))))),
        regex_para_matcher!(
            r#"file INSTALL cannot copy file\n"(.*)"\sto\s"(.*)":\sNo space left on device.\n"#,
            |_m| Ok(Some(Box::new(NoSpaceOnDevice)))
        ),
        regex_para_matcher!(
            r"patch: \*\*\*\* write error : No space left on device", |_| Ok(Some(Box::new(NoSpaceOnDevice)))
        ),
        regex_para_matcher!(
            r".*\(No space left on device\)", |_| Ok(Some(Box::new(NoSpaceOnDevice)))
        ),
        regex_para_matcher!(r#"file INSTALL cannot copy file\n"(.*)"\nto\n"(.*)"\.\n"#),
        regex_para_matcher!(
            r#"Missing (.*)\.  Either your\nlib(.*) version is too old, or lib(.*) wasn\'t found in the place you\nsaid."#,
            |m| Ok(Some(Box::new(MissingLibrary(m.get(1).unwrap().as_str().to_string()))))
        ),
        regex_para_matcher!(
            r"need (.*) of version (.*)",
            |m| Ok(Some(Box::new(MissingVagueDependency{
                name: m.get(1).unwrap().as_str().to_string(),
                minimum_version: Some(m.get(2).unwrap().as_str().to_string()),
                url: None,
                current_version: None
            })))
        ),
        regex_para_matcher!(
            r"\*\*\* (.*) is required to build (.*)\n",
            |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))
        ),
        regex_para_matcher!(r"\[([^ ]+)\] not found", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))),
        regex_para_matcher!(r"([^ ]+) not found", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))),
        regex_para_matcher!(r"error: could not find git .*", |_m| Ok(Some(Box::new(MissingCommand("git".to_string()))))),
        regex_para_matcher!(
            r"Could not find \'(.*)\' executable[\!,].*",
            |m| Ok(Some(Box::new(MissingCommand(m.get(1).unwrap().as_str().to_string()))))
        ),
        regex_para_matcher!(
            r"Could not find (.*)_STATIC_LIBRARIES using the following names: ([a-zA-z0-9_.]+)",
            |m| Ok(Some(Box::new(MissingStaticLibrary{
                library: m.get(1).unwrap().as_str().to_string(),
                filename: m.get(2).unwrap().as_str().to_string()})))
        ),
        regex_para_matcher!(
            "include could not find (requested|load) file:\n\n  (.*)\n",
            |m| {
                let mut path = m.get(2).unwrap().as_str().to_string();
                if !path.ends_with(".cmake") {
                    path += ".cmake";
                }
                Ok(Some(Box::new(CMakeFilesMissing{filenames:vec![path], version: None })))
            }
        ),
        regex_para_matcher!(r"(.*) and (.*) are required", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))),
        regex_para_matcher!(
            r"Please check your (.*) installation",
            |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))
        ),
        regex_para_matcher!(r"Python module (.*) not found\!", |m| Ok(Some(Box::new(MissingPythonModule::simple(m.get(1).unwrap().as_str().to_string()))))),
        regex_para_matcher!(r"\s*could not find ([^\s]+)$", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))),
        regex_para_matcher!(
            r"Please install (.*) before installing (.*)\.",
            |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))
        ),
        regex_para_matcher!(
            r"Please get (.*) from (www\..*)",
            |m| Ok(Some(Box::new(MissingVagueDependency {
                name: m.get(1).unwrap().as_str().to_string(),
                url: Some(m.get(2).unwrap().as_str().to_string()),
                minimum_version: None,
                current_version: None
            })))
        ),
        regex_para_matcher!(
            r#"Found unsuitable Qt version "" from NOTFOUND, this code requires Qt 4.x"#,
            |_| Ok(Some(Box::new(MissingQt)))
        ),
        regex_para_matcher!(
            r"(.*) executable not found\! Please install (.*)\.",
            |m| Ok(Some(Box::new(MissingCommand(m.get(1).unwrap().as_str().to_string()))))
        ),
        regex_para_matcher!(r"(.*) tool not found", |m| Ok(Some(Box::new(MissingCommand(m.get(1).unwrap().as_str().to_string()))))),
        regex_para_matcher!(
            r"--   Requested \'(.*) >= (.*)\' but version of (.*) is (.*)",
            |m| Ok(Some(Box::new(MissingPkgConfig{
                module: m.get(1).unwrap().as_str().to_string(),
                minimum_version: Some(m.get(2).unwrap().as_str().to_string())
            })))
        ),
        regex_para_matcher!(r"--   No package \'(.*)\' found", |m| Ok(Some(Box::new(MissingPkgConfig{minimum_version: None, module: m.get(1).unwrap().as_str().to_string()})))),
        regex_para_matcher!(r"([^ ]+) library not found\.", |m| Ok(Some(Box::new(MissingLibrary(m.get(1).unwrap().as_str().to_string()))))),
        regex_para_matcher!(
            r"Please install (.*) so that it is on the PATH and try again\.",
            command_missing
        ),
        regex_para_matcher!(
            r"-- Unable to find git\.  Setting git revision to \'unknown\'\.",
            |_| Ok(Some(Box::new(MissingCommand("git".to_string()))))
        ),
        regex_para_matcher!(
            r"(.*) must be installed before configuration \& building can proceed",
            |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))
        ),
        regex_para_matcher!(
            r"(.*) development files not found\.",
            |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))
        ),
        regex_para_matcher!(
            r".* but no (.*) dev libraries found",
            |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))
        ),
        regex_para_matcher!(
            r"Failed to find (.*) \(missing: .*\)",
            |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))
        ),
        regex_para_matcher!(
            r"Couldn\'t find ([^ ]+) development files\..*",
            |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))
        ),
        regex_para_matcher!(
            r"Could not find required (.*) package\!",
            |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))
        ),
        regex_para_matcher!(
            r"Cannot find (.*), giving up\. .*",
            |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))
        ),
        regex_para_matcher!(
            r"Cannot find (.*)\. (.*) is required for (.*)",
            |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))
        ),
        regex_para_matcher!(
            r"The development\sfiles\sfor\s(.*)\sare\srequired\sto\sbuild (.*)\.",
            |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))
        ),
        regex_para_matcher!(
            r"Required library (.*) not found\.",
            |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))
        ),
        regex_para_matcher!(
            r"(.*) required to compile (.*)",
            |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))
        ),
        regex_para_matcher!(
            r"(.*) requires (.*) ([0-9].*) or newer. See (https://.*)\s*",
            |m| Ok(Some(Box::new(MissingVagueDependency {
                name: m.get(2).unwrap().as_str().to_string(),
                minimum_version: Some(m.get(3).unwrap().as_str().to_string()),
                url: Some(m.get(4).unwrap().as_str().to_string()),
                current_version: None
            })))
        ),
        regex_para_matcher!(
            r"(.*) requires (.*) ([0-9].*) or newer.\s*",
            |m| Ok(Some(Box::new(MissingVagueDependency{
                name: m.get(2).unwrap().as_str().to_string(),
                minimum_version: Some(m.get(3).unwrap().as_str().to_string()),
                url: None,
                current_version: None
            })))
        ),
        regex_para_matcher!(r"(.*) requires (.*) to build", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(2).unwrap().as_str()))))),
        regex_para_matcher!(r"(.*) library missing", |m| Ok(Some(Box::new(MissingLibrary(m.get(1).unwrap().as_str().to_string()))))),
        regex_para_matcher!(r"(.*) requires (.*)", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(2).unwrap().as_str()))))),
        regex_para_matcher!(r"Could not find ([A-Za-z-]+)", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))),
        regex_para_matcher!(r"(.+) is required for (.*)\.", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))),
        regex_para_matcher!(
            r"No (.+) version could be found in your system\.",
            |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))
        ),
        regex_para_matcher!(
            r"([^ ]+) >= (.*) is required",
            |m| Ok(Some(Box::new(MissingVagueDependency {
                name: m.get(1).unwrap().as_str().to_string(),
                minimum_version: Some(m.get(2).unwrap().as_str().to_string()),
                current_version: None,
                url: None
            })))
        ),
        regex_para_matcher!(r"\s*([^ ]+) is required", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))),
        regex_para_matcher!(r"([^ ]+) binary not found\!", |m| Ok(Some(Box::new(MissingCommand(m.get(1).unwrap().as_str().to_string()))))),
        regex_para_matcher!(r"error: could not find git for clone of .*", |_m| Ok(Some(Box::new(MissingCommand("git".to_string()))))),
        regex_para_matcher!(r"Did not find ([^\s]+)", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))),
        regex_para_matcher!(
            r"Could not find the ([^ ]+) external dependency\.",
            |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))
        ),
        regex_para_matcher!(r"Couldn\'t find (.*)", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))),
    ]);
}

#[derive(Debug, Clone)]
struct CMakeErrorMatcher;

// Function to extract error lines and corresponding line numbers
fn extract_cmake_error_lines<'a>(lines: &'a [&'a str], i: usize) -> (Vec<usize>, String) {
    let mut linenos = vec![i];
    let mut error_lines = vec![];

    // Iterate over the lines starting from index i + 1
    for (j, line) in lines.iter().enumerate().skip(i + 1) {
        let trimmed = line.trim_end_matches('\n');
        if !trimmed.is_empty() && !line.starts_with(' ') {
            break;
        }
        error_lines.push(*line);
        linenos.push(j);
    }

    // Remove trailing empty lines from error_lines and linenos
    while let Some(last_line) = error_lines.last() {
        if last_line.trim_end_matches('\n').is_empty() {
            error_lines.pop();
            linenos.pop();
        } else {
            break;
        }
    }

    // Dedent the error_lines using textwrap::dedent
    let dedented_string = textwrap::dedent(&error_lines.join(""));
    (linenos, dedented_string)
}

impl Matcher for CMakeErrorMatcher {
    fn extract_from_lines(
        &self,
        lines: &[&str],
        offset: usize,
    ) -> Result<Option<(Box<dyn Match>, Option<Box<dyn Problem>>)>, Error> {
        let (_path, _start_linenos) = if let Some((_, _, path, start_lineno, _)) = lazy_regex::regex_captures!(
            r"CMake (Error|Warning) at (.+):([0-9]+) \((.*)\):",
            lines[offset].trim_end_matches('\n')
        ) {
            (path, start_lineno.parse::<usize>().unwrap())
        } else {
            return Ok(None);
        };

        let (linenos, error_string) = extract_cmake_error_lines(lines, offset);

        let mut actual_lines: Vec<_> = vec![];
        for lineno in &linenos {
            actual_lines.push(lines[*lineno].to_string());
        }

        let r#match = Box::new(MultiLineMatch::new(
            Origin("CMake".to_string()),
            linenos,
            actual_lines,
        ));

        if let Some((_match, problem)) =
            CMAKE_ERROR_MATCHERS.extract_from_lines(&[&error_string], 0)?
        {
            Ok(Some((r#match, problem)))
        } else {
            Ok(Some((r#match, None)))
        }
    }
}

/// Attempts to match and identify problems in the given lines.
///
/// This function applies common matchers to the specified lines to identify
/// known problems in build logs.
///
/// # Arguments
/// * `lines` - The log lines to analyze
/// * `offset` - The offset at which to start analyzing
///
/// # Returns
/// A result containing either:
/// * `Ok(Some((match, problem)))` - A match was found, possibly with a problem
/// * `Ok(None)` - No match was found
/// * `Err(error)` - An error occurred during matching
pub fn match_lines(lines: &[&str], offset: usize) -> MatcherResult {
    COMMON_MATCHERS.extract_from_lines(lines, offset)
}

macro_rules! secondary_matcher {
    ($re:expr) => {
        fancy_regex::Regex::new($re).unwrap()
    };
}

lazy_static::lazy_static! {
    /// Regexps that hint at an error of some sort, but not the error itself.
    static ref SECONDARY_MATCHERS: Vec<fancy_regex::Regex> = vec![
    secondary_matcher!(r"E: pybuild pybuild:[0-9]+: test: plugin [^ ]+ failed with:"),
    secondary_matcher!(r"[^:]+: error: (.*)"),
    secondary_matcher!(r"[^:]+:[0-9]+: error: (.*)"),
    secondary_matcher!(r"[^:]+:[0-9]+:[0-9]+: error: (.*)"),
    secondary_matcher!(r"error TS[0-9]+: (.*)"),

    secondary_matcher!(r"mount: .*: mount failed: Operation not permitted\."),

    secondary_matcher!(r"  [0-9]+:[0-9]+\s+error\s+.+"),

    secondary_matcher!(r"fontmake: Error: In '(.*)': (.*)"),

    secondary_matcher!(r"#   Failed test at t\/.*\.t line [0-9]+\."),

    secondary_matcher!(r"Gradle build daemon disappeared unexpectedly \(it may have been killed or may have crashed\)"),

    // ocaml
    secondary_matcher!(r"\*\*\* omake error:"),
    secondary_matcher!(r".*ocamlc.*: OCam has been configured with -force-safe-string: -unsafe-string is not available\."),

    // latex
    secondary_matcher!(r"\! LaTeX Error: .*"),

    secondary_matcher!(r"Killed"),

    // Java
    secondary_matcher!(r#"Exception in thread "(.*)" (.*): (.*);"#),
    secondary_matcher!(r"error: Unrecognized option: \'.*\'"),
    secondary_matcher!(r"Segmentation fault"),
    secondary_matcher!(r"\[ERROR\] (.*\.java):\[[0-9]+,[0-9]+\] (.*)"),
    secondary_matcher!(r"make: \*\*\* No targets specified and no makefile found\.  Stop\."),
    secondary_matcher!(r"make\[[0-9]+\]: \*\*\* No targets specified and no makefile found\.  Stop\."),
    secondary_matcher!(r"make\[[0-9]+\]: (.*): No such file or directory"),
    secondary_matcher!(r"make\[[0-9]+\]: \*\*\* \[.*:[0-9]+: .*\] Segmentation fault"),
    secondary_matcher!(
    r".*:[0-9]+: \*\*\* empty variable name.  Stop."),
    secondary_matcher!(
    r"error: can't copy '(.*)': doesn't exist or not a regular file"),
    secondary_matcher!(
    r"error: ([0-9]+) test executed, ([0-9]+) fatal tests failed, "),
    secondary_matcher!(
    r"([0-9]+) nonfatal test failed\."),
    secondary_matcher!(
    r".*\.rst:toctree contains ref to nonexisting file \'.*\'"),
    secondary_matcher!(
    r".*\.rst:[0-9]+:term not in glossary: .*"),
    secondary_matcher!(
    r"Try adding AC_PREREQ\(\[(.*)\]\) to your configure\.ac\."),
    // Erlang
    secondary_matcher!(
    r"  (.*_test): (.+)\.\.\.\*failed\*"),
    secondary_matcher!(
    r"(.*\.erl):[0-9]+:[0-9]+: erlang:.*"),
    // Clojure
    secondary_matcher!(
    r"Could not locate (.*) or (.*) on classpath\."),
    // QMake
    secondary_matcher!(
    r"Project ERROR: .*"),
    // pdflatex
    secondary_matcher!(
    r"\!  ==> Fatal error occurred, no output PDF file produced\!"),
    // latex
    secondary_matcher!(
    r"\! Undefined control sequence\."),
    secondary_matcher!(
    r"\! Emergency stop\."),
    secondary_matcher!(r"\!pdfTeX error: pdflatex: fwrite\(\) failed"),
    // inkscape
    secondary_matcher!(r"Unknown option (?!.*ignoring.*)"),
    // CTest
    secondary_matcher!(
    r"not ok [0-9]+ .*"),
    secondary_matcher!(
    r"Errors while running CTest"),
    secondary_matcher!(
    r"dh_auto_install: error: .*"),
    secondary_matcher!(
    r"dh_quilt_patch: error: (.*)"),
    secondary_matcher!(
    r"dh.*: Aborting due to earlier error"),
    secondary_matcher!(
    r"dh.*: unknown option or error during option parsing; aborting"),
    secondary_matcher!(
    r"Could not import extension .* \(exception: .*\)"),
    secondary_matcher!(
    r"configure.ac:[0-9]+: error: (.*)"),
    secondary_matcher!(
    r"Reconfigure the source tree (via './config' or 'perl Configure'), please."),
    secondary_matcher!(
    r"dwz: Too few files for multifile optimization"),
    secondary_matcher!(
    r"\[CJM/MatchManifest\] Aborted because of MANIFEST mismatch"),
    secondary_matcher!(
    r"dh_dwz: dwz -q -- .* returned exit code [0-9]+"),
    secondary_matcher!(
    r"help2man: can\'t get `-?-help\' info from .*"),
    secondary_matcher!(
    r"[^:]+: line [0-9]+:\s+[0-9]+ Segmentation fault.*"),
    secondary_matcher!(
    r"dpkg-gencontrol: error: (.*)"),
    secondary_matcher!(
    r".*:[0-9]+:[0-9]+: (error|ERROR): (.*)"),
    secondary_matcher!(
    r".*[.]+FAILED .*"),
    secondary_matcher!(
    r"FAIL: (.*)"),
    secondary_matcher!(
    r"FAIL\!  : (.*)"),
    secondary_matcher!(
    r"\s*FAIL (.*) \(.*\)"),
    secondary_matcher!(
    r"FAIL\s+(.*) \[.*\] ?"),
    secondary_matcher!(
    r"([0-9]+)% tests passed, ([0-9]+) tests failed out of ([0-9]+)"),
    secondary_matcher!(
    r"TEST FAILURE"),
    secondary_matcher!(
    r"make\[[0-9]+\]: \*\*\* \[.*\] Error [0-9]+"),
    secondary_matcher!(
    r"make\[[0-9]+\]: \*\*\* \[.*\] Aborted"),
    secondary_matcher!(
    r"exit code=[0-9]+: .*"),
    secondary_matcher!(
    r"chmod: cannot access \'.*\': .*"),
    secondary_matcher!(
    r"dh_autoreconf: autoreconf .* returned exit code [0-9]+"),
    secondary_matcher!(
    r"make: \*\*\* \[.*\] Error [0-9]+"),
    secondary_matcher!(
    r".*:[0-9]+: \*\*\* missing separator\.  Stop\."),
    secondary_matcher!(
    r"[0-9]+ tests: [0-9]+ ok, [0-9]+ failure\(s\), [0-9]+ test\(s\) skipped"),
    secondary_matcher!(
    r"\*\*Error:\*\* (.*)"),
    secondary_matcher!(
    r"^Error: (.*)"),
    secondary_matcher!(
    r"Failed [0-9]+ tests? out of [0-9]+, [0-9.]+% okay."),
    secondary_matcher!(
    r"Failed [0-9]+\/[0-9]+ test programs. [0-9]+/[0-9]+ subtests failed."),
    secondary_matcher!(
    r"Original error was: (.*)"),
    secondary_matcher!(
    r"-- Error \(.*\.R:[0-9]+:[0-9]+\): \(.*\) [-]*"),
    secondary_matcher!(
    r"^Error \[ERR_.*\]: .*"),
    secondary_matcher!(
    r"^FAILED \(.*\)"),
    secondary_matcher!(
    r"FAILED .*"),
    // Random Python errors
    secondary_matcher!(
    "^(E  +)?(SyntaxError|TypeError|ValueError|AttributeError|NameError|django.core.exceptions..*|RuntimeError|subprocess.CalledProcessError|testtools.matchers._impl.MismatchError|PermissionError|IndexError|TypeError|AssertionError|IOError|ImportError|SerialException|OSError|qtawesome.iconic_font.FontError|redis.exceptions.ConnectionError|builtins.OverflowError|ArgumentError|httptools.parser.errors.HttpParserInvalidURLError|HypothesisException|SSLError|KeyError|Exception|rnc2rng.parser.ParseError|pkg_resources.UnknownExtra|tarfile.ReadError|numpydoc.docscrape.ParseError|distutils.errors.DistutilsOptionError|datalad.support.exceptions.IncompleteResultsError|AssertionError|Cython.Compiler.Errors.CompileError|UnicodeDecodeError|UnicodeEncodeError): .*"),
    // Rust
    secondary_matcher!(
    r"error\[E[0-9]+\]: .*"),
    secondary_matcher!(
    "^E   DeprecationWarning: .*"),
    secondary_matcher!(
    "^E       fixture '(.*)' not found"),
    // Rake
    secondary_matcher!(
    r"[0-9]+ runs, [0-9]+ assertions, [0-9]+ failures, [0-9]+ errors, [0-9]+ skips"),
    // Node
    secondary_matcher!(
    r"# failed [0-9]+ of [0-9]+ tests"),
    // Pytest
    secondary_matcher!(
    r"(.*).py:[0-9]+: AssertionError"),
    secondary_matcher!(
    r"============================ no tests ran in ([0-9.]+)s ============================="),
    // Perl
    secondary_matcher!(
    r"  Failed tests:  [0-9-]+"),
    secondary_matcher!(
    r"Failed (.*\.t): output changed"),
    // Go
    secondary_matcher!(
    r"no packages to test"),
    secondary_matcher!(
    "FAIL\t(.*)\t[0-9.]+s"),
    secondary_matcher!(
    r".*.go:[0-9]+:[0-9]+: (?!note:).*"),
    secondary_matcher!(
    r"can\'t load package: package \.: no Go files in /<<PKGBUILDDIR>>/(.*)"),
    // Ld
    secondary_matcher!(
    r"\/usr\/bin\/ld: cannot open output file (.*): No such file or directory"),
    secondary_matcher!(
    r"configure: error: (.+)"),
    secondary_matcher!(
    r"config.status: error: (.*)"),
    secondary_matcher!(
    r"E: Build killed with signal TERM after ([0-9]+) minutes of inactivity"),
    secondary_matcher!(
    r"    \[javac\] [^: ]+:[0-9]+: error: (.*)"),
    secondary_matcher!(
    r"1\) TestChannelFeature: ([^:]+):([0-9]+): assert failed"),
    secondary_matcher!(
    r"cp: target \'(.*)\' is not a directory"),
    secondary_matcher!(
    r"cp: cannot create regular file \'(.*)\': No such file or directory"),
    secondary_matcher!(
    r"couldn\'t determine home directory at (.*)"),
    secondary_matcher!(
    r"ln: failed to create symbolic link \'(.*)\': File exists"),
    secondary_matcher!(
    r"ln: failed to create symbolic link \'(.*)\': No such file or directory"),
    secondary_matcher!(
    r"ln: failed to create symbolic link \'(.*)\': Permission denied"),
    secondary_matcher!(
    r"ln: invalid option -- .*"),
    secondary_matcher!(
    r"mkdir: cannot create directory [‘'](.*)['’]: No such file or directory"),
    secondary_matcher!(
    r"mkdir: cannot create directory [‘'](.*)['’]: File exists"),
    secondary_matcher!(
    r"mkdir: missing operand"),
    secondary_matcher!(
    r"rmdir: failed to remove '.*': No such file or directory"),
    secondary_matcher!(
    r"Fatal error: .*"),
    secondary_matcher!(
    "Fatal Error: (.*)"),
    secondary_matcher!(
    r"Alert: (.*)"),
    secondary_matcher!(
    r#"ERROR: Test "(.*)" failed. Exiting."#),
    // scons
    secondary_matcher!(
    r"ERROR: test\(s\) failed in (.*)"),
    secondary_matcher!(
    r"./configure: line [0-9]+: syntax error near unexpected token `.*\'"),
    secondary_matcher!(
    r"scons: \*\*\* \[.*\] ValueError : unsupported pickle protocol: .*"),
    // yarn
    secondary_matcher!(
    r"ERROR: There are no scenarios; must have at least one."),
    // perl
    secondary_matcher!(
    r"Execution of (.*) aborted due to compilation errors."),
    // Mocha
    secondary_matcher!(
    r"     AssertionError \[ERR_ASSERTION\]: Missing expected exception."),
    // lt (C++)
    secondary_matcher!(
    r".*: .*:[0-9]+: .*: Assertion `.*\' failed."),
    secondary_matcher!(
    r"(.*).xml: FAILED:"),
    secondary_matcher!(
    r" BROKEN .*"),
    secondary_matcher!(
    r"failed: [0-9]+-.*"),
    // ninja
    secondary_matcher!(
    r"ninja: build stopped: subcommand failed."),
    secondary_matcher!(
    r".*\.s:[0-9]+: Error: .*"),
    // rollup
    secondary_matcher!(r"\[\!\] Error: Unexpected token"),
    // glib
    secondary_matcher!(r"\(.*:[0-9]+\): [a-zA-Z0-9]+-CRITICAL \*\*: [0-9:.]+: .*"),
    secondary_matcher!(
    r"tar: option requires an argument -- \'.\'"),
    secondary_matcher!(
    r"tar: .*: Cannot stat: No such file or directory"),
    secondary_matcher!(
    r"tar: .*: Cannot open: No such file or directory"),
    // rsvg-convert
    secondary_matcher!(
    r"Could not render file (.*.svg)"),
    // pybuild tests
    secondary_matcher!(
    r"ERROR: file not found: (.*)"),
    // msgfmt
    secondary_matcher!(
    r"/usr/bin/msgfmt: found [0-9]+ fatal errors"),
    // Docker
    secondary_matcher!(
    r"Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running\?"),
    secondary_matcher!(
    r"dh_makeshlibs: failing due to earlier errors"),
    // Ruby
    secondary_matcher!(
    r"([^:]+)\.rb:[0-9]+:in `([^\'])+\': (.*) \((.*)\)"),
    secondary_matcher!(
    r".*: \*\*\* ERROR: There where errors/warnings in server logs after running test cases."),
    secondary_matcher!(
    r"Errno::EEXIST: File exists @ dir_s_mkdir - .*"),
    secondary_matcher!(
    r"Test environment was found to be incomplete at configuration time,"),
    secondary_matcher!(
    r"libtool:   error: cannot find the library \'(.*)\' or unhandled argument \'(.*)\'"),
    secondary_matcher!(
    r"npm ERR\! (.*)"),
    secondary_matcher!(
    r"install: failed to access \'(.*)\': (.*)"),
    secondary_matcher!(
    r"MSBUILD: error MSBUILD[0-9]+: Project file \'(.*)\' not found."),
    secondary_matcher!(
    r"E: (.*)"),
    secondary_matcher!(
    r"(.*)\(([0-9]+),([0-9]+)\): Error: .*"),
    // C #
    secondary_matcher!(
    r"(.*)\.cs\([0-9]+,[0-9]+\): error CS[0-9]+: .*"),
    secondary_matcher!(
    r".*Segmentation fault.*"),
    secondary_matcher!(
    r"a2x: ERROR: (.*) returned non-zero exit status ([0-9]+)"),
    secondary_matcher!(
    r"-- Configuring incomplete, errors occurred\!"),
    secondary_matcher!(
    r#"Error opening link script "(.*)""#),
    secondary_matcher!(
    r"cc: error: (.*)"),
    secondary_matcher!(
    r"\[ERROR\] .*"),
    secondary_matcher!(
    r"dh_auto_(test|build): error: (.*)"),
    secondary_matcher!(
    r"tar: This does not look like a tar archive"),
    secondary_matcher!(
    r"\[DZ\] no (name|version) was ever set"),
    secondary_matcher!(
    r"\[Runtime\] No -phase or -relationship specified at .* line [0-9]+\."),
    secondary_matcher!(
    r"diff: (.*): No such file or directory"),
    secondary_matcher!(
    r"gpg: signing failed: .*"),
    // mh_install
    secondary_matcher!(
    r"Cannot find the jar to install: (.*)"),
    secondary_matcher!(
    r"ERROR: .*"),
    secondary_matcher!(
    r"> error: (.*)"),
    secondary_matcher!(
    r"error: (.*)"),
    secondary_matcher!(
    r"(.*\.hs):[0-9]+:[0-9]+: error:"),
    secondary_matcher!(
    r"go1: internal compiler error: .*"),
];
}

/// Finds secondary indicators of build failures in log lines.
///
/// This function looks for secondary patterns that suggest a build failure
/// but aren't the primary error message.
///
/// # Arguments
/// * `lines` - The log lines to analyze
/// * `start_offset` - The number of lines to consider from the end of the log
///
/// # Returns
/// An optional match for a potential build failure
pub fn find_secondary_build_failure(
    lines: &[&str],
    start_offset: usize,
) -> Option<SingleLineMatch> {
    for (offset, line) in lines.enumerate_tail_forward(start_offset) {
        let match_line = line.trim_end_matches('\n');
        for regexp in SECONDARY_MATCHERS.iter() {
            match regexp.is_match(match_line) {
                Ok(true) => {
                    let origin = Origin(format!("secondary regex {:?}", regexp));
                    log::debug!(
                        "Found match against {:?} on {:?} (line {})",
                        regexp,
                        line,
                        offset + 1
                    );
                    return Some(SingleLineMatch {
                        origin,
                        offset,
                        line: line.to_string(),
                    });
                }
                Ok(false) => {
                    // No match, continue to next regex
                }
                Err(fancy_regex::Error::RuntimeError(
                    fancy_regex::RuntimeError::BacktrackLimitExceeded,
                )) => {
                    // Handle backtracking limit exceeded gracefully
                    log::debug!(
                        "Regex backtracking limit exceeded for pattern '{}' on line (length: {})",
                        regexp.as_str(),
                        match_line.len()
                    );
                    // Continue to next regex
                }
                Err(other_error) => {
                    // Log other regex errors but continue processing
                    log::warn!(
                        "Regex error for pattern '{}': {}",
                        regexp.as_str(),
                        other_error
                    );
                    // Continue to next regex
                }
            }
        }
    }
    None
}

/// Find the key failure line in build output.
///
/// # Returns
/// A tuple with (match object, error object)
pub fn find_build_failure_description(lines: Vec<&str>) -> BuildFailureResult {
    pub const OFFSET: usize = 250;
    // Is this cmake-specific, or rather just kf5 / qmake ?
    let mut cmake = false;
    // We search backwards for clear errors.
    for (lineno, line) in lines.enumerate_backward(Some(250)) {
        if line.contains("cmake") {
            cmake = true;
        }
        if let Some((mm, merr)) = match_lines(lines.as_slice(), lineno).unwrap() {
            return (Some(mm), merr);
        }
    }

    // TODO(jelmer): Remove this in favour of CMakeErrorMatcher above.
    if cmake {
        // Urgh, multi-line regexes---
        for (mut lineno, line) in lines.enumerate_forward(None) {
            let line = line.trim_end_matches('\n');
            if let Some((_, target)) =
                lazy_regex::regex_captures!(r"  Could NOT find (.*) \(missing: .*\)", line)
            {
                return (
                    Some(Box::new(SingleLineMatch::from_lines(
                        &lines,
                        lineno,
                        Some("direct regex"),
                    )) as Box<dyn Match>),
                    Some(Box::new(MissingCommand(target.to_lowercase())) as Box<dyn Problem>),
                );
            }
            if let Some((_, _target)) = lazy_regex::regex_captures!(
                r#"\s*The imported target "(.*)" references the file"#,
                line
            ) {
                lineno += 1;
                while lineno < lines.len() && !line.is_empty() {
                    lineno += 1;
                }
                if lines[lineno + 2].starts_with("  but this file does not exist.") {
                    let filename = if let Some((_, entry)) =
                        lazy_regex::regex_captures!(r#"\s*"(.*)""#, line)
                    {
                        entry
                    } else {
                        line
                    };
                    return (
                        Some(Box::new(SingleLineMatch::from_lines(
                            &lines,
                            lineno,
                            Some("direct regex"),
                        )) as Box<dyn Match>),
                        Some(Box::new(MissingFile {
                            path: filename.into(),
                        }) as Box<dyn Problem>),
                    );
                }
                continue;
            }
            if lineno + 1 < lines.len() {
                if let Some((_, _pkg)) = lazy_regex::regex_captures!("^  Could not find a package configuration file provided by \"(.*)\" with any of the following names:", &(line.to_string() + " " + lines[lineno + 1].trim_start_matches(' ').trim_end_matches('\n'))) {
                    if lines[lineno + 2] == "\n" {
                        let mut i = 3;
                        let mut filenames = vec![];
                        while !lines[lineno + i].trim().is_empty() {
                            filenames.push(lines[lineno + i].trim().to_string());
                            i += 1;
                        }
                        return (
                            Some(Box::new(SingleLineMatch::from_lines(
                                &lines, lineno, Some("direct regex (cmake)")
                            )) as Box<dyn Match>),
                            Some(Box::new(CMakeFilesMissing{filenames, version: None}) as Box<dyn Problem>),
                        )
                    }
                }
            }
        }
    }

    // And forwards for vague ("secondary") errors.
    let m = find_secondary_build_failure(lines.as_slice(), OFFSET);
    if let Some(m) = m {
        return (Some(Box::new(m)), None);
    }

    (None, None)
}

#[cfg(test)]
mod tests {
    use super::*;

    fn assert_just_match(lines: Vec<&str>, lineno: usize) {
        let (r#match, actual_err) = super::find_build_failure_description(lines.clone());
        assert!(actual_err.is_none());
        if let Some(r#match) = r#match.as_ref() {
            assert_eq!(&r#match.line(), &lines[lineno - 1]);
            assert_eq!(lineno, r#match.lineno());
        } else {
            assert!(r#match.is_none());
        }
    }

    fn assert_match(lines: Vec<&str>, lineno: usize, mut expected: Option<impl Problem + 'static>) {
        let (r#match, actual_err) = super::find_build_failure_description(lines.clone());
        if let Some(r#match) = r#match.as_ref() {
            assert_eq!(&r#match.line(), &lines[lineno - 1]);
            assert_eq!(lineno, r#match.lineno());
        } else {
            assert!(r#match.is_none());
        }
        if let Some(expected) = expected.take() {
            assert!(
                r#match.is_some(),
                "err ({:?}) provided but match missing",
                &expected
            );
            assert_eq!(
                actual_err.as_ref().map(|x| x.as_ref()),
                Some(&expected as &dyn Problem)
            );
        } else {
            assert!(actual_err.is_none());
        }
    }

    #[test]
    fn test_make_missing_rule() {
        assert_match(
            vec![
                "make[1]: *** No rule to make target 'nno.autopgen.bin', needed by 'dan-nno.autopgen.bin'.  Stop."
            ],
            1,
            Some(MissingMakeTarget::new( "nno.autopgen.bin", Some("dan-nno.autopgen.bin"))),
        );

        assert_match(vec![
                "make[1]: *** No rule to make target '/usr/share/blah/blah', needed by 'dan-nno.autopgen.bin'.  Stop."
            ],
            1,
            Some(MissingMakeTarget::new("/usr/share/blah/blah", Some("dan-nno.autopgen.bin"))),
        );
        assert_match(
            vec![
                "debian/rules:4: /usr/share/openstack-pkg-tools/pkgos.make: No such file or directory"
            ],
            1,
            Some(MissingFile::new("/usr/share/openstack-pkg-tools/pkgos.make".into())),
        );
    }

    #[test]
    fn test_git_identity() {
        assert_match(
            vec![
                "fatal: unable to auto-detect email address (got 'jenkins@osuosl167-amd64.(none)')",
            ],
            1,
            Some(MissingGitIdentity),
        );
    }

    #[test]
    fn test_ioerror() {
        assert_match(
            vec![
                "E   IOError: [Errno 2] No such file or directory: '/usr/lib/python2.7/poly1305/rfc7539.txt'"
            ],
            1,
            Some(MissingFile::new("/usr/lib/python2.7/poly1305/rfc7539.txt".into())),
        );
    }

    #[test]
    fn test_vignette() {
        assert_match(
            vec![
                "Error: processing vignette 'uroot-intro.Rnw' failed with diagnostics:",
                "pdflatex is not available",
            ],
            2,
            Some(MissingVagueDependency::simple("pdflatex")),
        );
    }

    #[test]
    fn test_upstart_file_present() {
        assert_match(
            vec![
                "dh_installinit: upstart jobs are no longer supported!  Please remove debian/sddm.upstart and check if you need to add a conffile removal"
            ],
            1,
            Some(UpstartFilePresent("debian/sddm.upstart".into())),
        );
    }

    #[test]
    fn test_missing_go_mod_file() {
        assert_match(
            vec![
                "go: go.mod file not found in current directory or any parent directory; see 'go help modules'"
            ],
            1,
            Some(MissingGoModFile),
        );
    }

    #[test]
    fn test_missing_javascript_runtime() {
        assert_match(
            vec![
                "ExecJS::RuntimeUnavailable: Could not find a JavaScript runtime. See https://github.com/rails/execjs for a list of available runtimes."],
            1,
            Some(MissingJavaScriptRuntime)
        );
    }

    #[test]
    fn test_directory_missing() {
        assert_match(
            vec!["debian/components/build: 19: cd: can't cd to rollup-plugin"],
            1,
            Some(DirectoryNonExistant("rollup-plugin".to_owned())),
        );
    }

    #[test]
    fn test_vcs_control_directory() {
        assert_match(
            vec!["   > Cannot find '.git' directory"],
            1,
            Some(VcsControlDirectoryNeeded::new(vec!["git"])),
        );
    }

    #[test]
    fn test_missing_sprockets_file() {
        assert_match(
            vec![
                "Sprockets::FileNotFound: couldn't find file 'activestorage' with type 'application/javascript'"
            ],
            1,
            Some(MissingSprocketsFile { name: "activestorage".to_owned(), content_type: "application/javascript".to_owned()}),
        );
    }

    #[test]
    fn test_gxx_missing_file() {
        assert_match(
            vec!["g++: error: /usr/lib/x86_64-linux-gnu/libGL.so: No such file or directory"],
            1,
            Some(MissingFile::new(
                "/usr/lib/x86_64-linux-gnu/libGL.so".into(),
            )),
        );
    }

    #[test]
    fn test_build_xml_missing_file() {
        assert_match(
            vec!["/<<PKGBUILDDIR>>/build.xml:59: /<<PKGBUILDDIR>>/lib does not exist."],
            1,
            Some(MissingBuildFile {
                filename: "lib".to_owned(),
            }),
        );
    }

    #[test]
    fn test_vignette_builder() {
        assert_match(
            vec!["  vignette builder 'R.rsp' not found"],
            1,
            Some(MissingRPackage::simple("R.rsp")),
        );
    }

    #[test]
    fn test_dh_missing_addon() {
        assert_match(
            vec![
                "   dh_auto_clean -O--buildsystem=pybuild",
                "E: Please add appropriate interpreter package to Build-Depends, see pybuild(1) for details.this: $VAR1 = bless( {",
                "     'py3vers' => '3.8',",
                "     'py3def' => '3.8',",
                "     'pyvers' => '',",
                "     'parallel' => '2',",
                "     'cwd' => '/<<PKGBUILDDIR>>',",
                "     'sourcedir' => '.',",
                "     'builddir' => undef,",
                "     'pypydef' => '',",
                "     'pydef' => ''",
                "   }, 'Debian::Debhelper::Buildsystem::pybuild' );",
                "deps: $VAR1 = [];",
            ],
            2,
            Some(DhAddonLoadFailure{ name: "pybuild".to_owned(), path: "Debian/Debhelper/Buildsystem/pybuild.pm".to_owned()}),
        );
    }

    #[test]
    fn test_libtoolize_missing_file() {
        assert_match(
            vec!["libtoolize:   error: '/usr/share/aclocal/ltdl.m4' does not exist."],
            1,
            Some(MissingFile::new("/usr/share/aclocal/ltdl.m4".into())),
        );
    }

    #[test]
    fn test_ruby_missing_file() {
        assert_match(
            vec![
                "Error: Error: ENOENT: no such file or directory, open '/usr/lib/nodejs/requirejs/text.js'"
            ],
            1,
            Some(MissingFile::new("/usr/lib/nodejs/requirejs/text.js".into())),
        );
    }

    #[test]
    fn test_vcversioner() {
        assert_match(
            vec![
                "vcversioner: ['git', '--git-dir', '/build/tmp0tlam4pe/pyee/.git', 'describe', '--tags', '--long'] failed and '/build/tmp0tlam4pe/pyee/version.txt' isn't present."
            ],
            1,
            Some(MissingVcVersionerVersion),
        );
    }

    #[test]
    fn test_python_missing_file() {
        assert_match(
            vec![
                "python3.7: can't open file '/usr/bin/blah.py': [Errno 2] No such file or directory"
            ],
            1,
            Some(MissingFile::new("/usr/bin/blah.py".into())),
        );
        assert_match(
            vec!["python3.7: can't open file 'setup.py': [Errno 2] No such file or directory"],
            1,
            Some(MissingBuildFile::new("setup.py".into())),
        );
        assert_match(
            vec![
                "E           FileNotFoundError: [Errno 2] No such file or directory: '/usr/share/firmware-microbit-micropython/firmware.hex'"
            ],
            1,
            Some(MissingFile::new(
                "/usr/share/firmware-microbit-micropython/firmware.hex".into()
            )),
        );
    }

    #[test]
    fn test_vague() {
        assert_match(
            vec![
                "configure: error: Please install gnu flex from http://www.gnu.org/software/flex/",
            ],
            1,
            Some(MissingVagueDependency {
                name: "gnu flex".to_string(),
                url: Some("http://www.gnu.org/software/flex/".to_owned()),
                minimum_version: None,
                current_version: None,
            }),
        );
        assert_match(
            vec!["RuntimeError: cython is missing"],
            1,
            Some(MissingVagueDependency::simple("cython")),
        );
        assert_match(
            vec![
                "configure: error:",
                "",
                "        Unable to find the Multi Emulator Super System (MESS).",
            ],
            3,
            Some(MissingVagueDependency::simple(
                "the Multi Emulator Super System (MESS)",
            )),
        );
        assert_match(
            vec![
                "configure: error: libwandio 4.0.0 or better is required to compile this version of libtrace. If you have installed libwandio in a non-standard location please use LDFLAGS to specify the location of the library. WANDIO can be obtained from http://research.wand.net.nz/software/libwandio.php"
            ],
            1,
            Some(MissingVagueDependency{
                name: "libwandio".to_owned(),
                minimum_version: Some("4.0.0".to_owned()),
                current_version: None,
                url: None,
            }),
        );
        assert_match(
            vec![
                "configure: error: libpcap0.8 or greater is required to compile libtrace. If you have installed it in a non-standard location please use LDFLAGS to specify the location of the library"
            ],
            1,
            Some(MissingVagueDependency::simple("libpcap0.8")),
        );
        assert_match(
            vec!["Error: Please install xml2 package"],
            1,
            Some(MissingVagueDependency::simple("xml2")),
        );
    }

    #[test]
    fn test_gettext_mismatch() {
        assert_match(
            vec![
                "*** error: gettext infrastructure mismatch: using a Makefile.in.in from gettext version 0.19 but the autoconf macros are from gettext version 0.20"
            ],
            1,
            Some(MismatchGettextVersions{makefile_version: "0.19".to_string(), autoconf_version: "0.20".to_string()}),
        );
    }

    #[test]
    fn test_x11_missing() {
        assert_match(
            vec![
                "configure: error: *** No X11! Install X-Windows development headers/libraries! ***"
            ],
            1,
            Some(MissingX11),
        );
    }

    #[test]
    fn test_multi_line_configure_error() {
        assert_just_match(
            vec!["configure: error:", "", "        Some other error."],
            3,
        );
        assert_match(
            vec![
                "configure: error:",
                "",
                "   Unable to find the Multi Emulator Super System (MESS).",
                "",
                "   Please install MESS, or specify the MESS command with",
                "   a MESS environment variable.",
                "",
                "e.g. MESS=/path/to/program/mess ./configure",
            ],
            3,
            Some(MissingVagueDependency::simple(
                "the Multi Emulator Super System (MESS)",
            )),
        );
    }

    #[test]
    fn test_interpreter_missing() {
        assert_match(
            vec![
                "/bin/bash: /usr/bin/rst2man: /usr/bin/python: bad interpreter: No such file or directory"
            ],
            1,
            Some(MissingFile::new("/usr/bin/python".into()))
        );
        assert_just_match(
            vec!["env: ‘/<<PKGBUILDDIR>>/socket-activate’: No such file or directory"],
            1,
        );
    }

    #[test]
    fn test_webpack_missing() {
        assert_just_match(
            vec![
                "ERROR in Entry module not found: Error: Can't resolve 'index.js' in '/<<PKGBUILDDIR>>'"
            ],
            1,
        );
    }

    #[test]
    fn test_installdocs_missing() {
        assert_match(
            vec![
                r#"dh_installdocs: Cannot find (any matches for) "README.txt" (tried in ., debian/tmp)"#,
            ],
            1,
            Some(DebhelperPatternNotFound {
                pattern: "README.txt".to_owned(),
                tool: "installdocs".to_owned(),
                directories: vec![".".to_string(), "debian/tmp".to_owned()],
            }),
        );
    }

    #[test]
    fn test_dh_compat_dupe() {
        assert_match(
            vec![
                "dh_autoreconf: debhelper compat level specified both in debian/compat and via build-dependency on debhelper-compat"
            ],
            1,
            Some(DuplicateDHCompatLevel{command: "dh_autoreconf".to_owned()}),
        );
    }

    #[test]
    fn test_dh_compat_missing() {
        assert_match(
            vec!["dh_clean: Please specify the compatibility level in debian/compat"],
            1,
            Some(MissingDHCompatLevel {
                command: "dh_clean".to_owned(),
            }),
        );
    }

    #[test]
    fn test_dh_compat_too_old() {
        assert_match(
            vec! [
                "dh_clean: error: Compatibility levels before 7 are no longer supported (level 5 requested)"
            ],
            1,
            Some(UnsupportedDebhelperCompatLevel{ oldest_supported: 7, requested: 5})
        );
    }

    #[test]
    fn test_dh_udeb_shared_library() {
        assert_just_match(vec![
                "dh_makeshlibs: The udeb libepoxy0-udeb (>= 1.3) does not contain any shared libraries but --add-udeb=libepoxy0-udeb (>= 1.3) was passed!?"
            ],
            1,
        );
    }

    #[test]
    fn test_dh_systemd() {
        assert_just_match(
            vec![
                "dh: unable to load addon systemd: dh: The systemd-sequence is no longer provided in compat >= 11, please rely on dh_installsystemd instead"
            ],
            1,
        );
    }

    #[test]
    fn test_dh_before() {
        assert_just_match(vec![
                "dh: The --before option is not supported any longer (#932537). Use override targets instead."
            ],
            1,
        );
    }

    #[test]
    fn test_meson_missing_git() {
        assert_match(
            vec!["meson.build:13:0: ERROR: Git program not found."],
            1,
            Some(MissingCommand("git".to_owned())),
        );
    }

    #[test]
    fn test_meson_missing_lib() {
        assert_match(
            vec!["meson.build:85:0: ERROR: C++ shared or static library 'vulkan-1' not found"],
            1,
            Some(MissingLibrary("vulkan-1".to_owned())),
        );
    }

    #[test]
    fn test_ocaml_library_missing() {
        assert_match(
            vec![r#"Error: Library "camlp-streams" not found."#],
            1,
            Some(MissingOCamlPackage("camlp-streams".to_owned())),
        );
    }

    #[test]
    fn test_meson_version() {
        assert_match(
            vec!["meson.build:1:0: ERROR: Meson version is 0.49.2 but project requires >=0.50"],
            1,
            Some(MissingVagueDependency {
                name: "meson".to_owned(),
                minimum_version: Some("0.50".to_owned()),
                current_version: Some("0.49.2".to_owned()),
                url: None,
            }),
        );
        assert_match(
            vec!["../meson.build:1:0: ERROR: Meson version is 0.49.2 but project requires >=0.50"],
            1,
            Some(MissingVagueDependency {
                name: "meson".to_string(),
                minimum_version: Some("0.50".to_owned()),
                current_version: Some("0.49.2".to_owned()),
                url: None,
            }),
        );
    }

    #[test]
    fn test_need_pgbuildext() {
        assert_match(
            vec![
                "Error: debian/control needs updating from debian/control.in. Run 'pg_buildext updatecontrol'."
            ],
            1,
            Some(NeedPgBuildExtUpdateControl{generated_path: "debian/control".to_owned(), template_path: "debian/control.in".to_owned()})
        );
    }

    #[test]
    fn test_cmake_missing_command() {
        assert_match(
            vec![
                "  Could NOT find Git (missing: GIT_EXECUTABLE)",
                "dh_auto_configure: cd obj-x86_64-linux-gnu && cmake with args",
            ],
            1,
            Some(MissingCommand("git".to_owned())),
        );
    }

    #[test]
    fn test_autoconf_version() {
        assert_match(
            vec!["configure.ac:13: error: Autoconf version 2.71 or higher is required"],
            1,
            Some(MissingVagueDependency {
                name: "autoconf".to_string(),
                minimum_version: Some("2.71".to_string()),
                current_version: None,
                url: None,
            }),
        );
    }

    #[test]
    fn test_claws_version() {
        assert_match(
            vec!["configure: error: libetpan 0.57 not found"],
            1,
            Some(MissingVagueDependency {
                name: "libetpan".to_string(),
                minimum_version: Some("0.57".to_string()),
                current_version: None,
                url: None,
            }),
        );
    }

    #[test]
    fn test_config_status_input() {
        assert_match(
            vec!["config.status: error: cannot find input file: `po/Makefile.in.in'"],
            1,
            Some(MissingConfigStatusInput {
                path: "po/Makefile.in.in".to_owned(),
            }),
        );
    }

    #[test]
    fn test_jvm() {
        assert_match(
            vec!["ERROR: JAVA_HOME is set to an invalid directory: /usr/lib/jvm/default-java/"],
            1,
            Some(MissingJVM),
        );
    }

    #[test]
    fn test_cp() {
        assert_match(
            vec![
                "cp: cannot stat '/<<PKGBUILDDIR>>/debian/patches/lshw-gtk.desktop': No such file or directory"
            ],
            1,
            Some(MissingBuildFile::new("debian/patches/lshw-gtk.desktop".to_owned()))
        );
    }

    #[test]
    fn test_bash_redir_missing() {
        assert_match(
            vec!["/bin/bash: idna-tables-properties.csv: No such file or directory"],
            1,
            Some(MissingBuildFile::new(
                "idna-tables-properties.csv".to_owned(),
            )),
        );
    }

    #[test]
    fn test_automake_input() {
        assert_match(
            vec!["automake: error: cannot open < gtk-doc.make: No such file or directory"],
            1,
            Some(MissingAutomakeInput {
                path: "gtk-doc.make".to_owned(),
            }),
        );
    }

    #[test]
    fn test_shellcheck() {
        assert_just_match(
            vec![
                &(" ".repeat(40)
                    + "^----^ SC2086: Double quote to prevent globbing and word splitting."),
            ],
            1,
        );
    }

    #[test]
    fn test_autoconf_macro() {
        assert_match(
            vec!["configure.in:1802: error: possibly undefined macro: AC_CHECK_CCA"],
            1,
            Some(MissingAutoconfMacro {
                r#macro: "AC_CHECK_CCA".to_owned(),
                need_rebuild: false,
            }),
        );
        assert_match(
            vec!["./configure: line 12569: PKG_PROG_PKG_CONFIG: command not found"],
            1,
            Some(MissingAutoconfMacro {
                r#macro: "PKG_PROG_PKG_CONFIG".to_owned(),
                need_rebuild: false,
            }),
        );
        assert_match(
            vec![
                "checking for gawk... (cached) mawk",
                "./configure: line 2368: syntax error near unexpected token `APERTIUM,'",
                "./configure: line 2368: `PKG_CHECK_MODULES(APERTIUM, apertium >= 3.7.1)'",
            ],
            3,
            Some(MissingAutoconfMacro {
                r#macro: "PKG_CHECK_MODULES".to_owned(),
                need_rebuild: true,
            }),
        );
        assert_match(
            vec![
                "checking for libexif to use... ./configure: line 15968: syntax error near unexpected token `LIBEXIF,libexif'",
                "./configure: line 15968: `\t\t\t\t\t\tPKG_CHECK_MODULES(LIBEXIF,libexif >= 0.6.18,have_LIBEXIF=yes,:)'",
            ],
            2,
            Some(MissingAutoconfMacro{
                r#macro: "PKG_CHECK_MODULES".to_owned(), need_rebuild:true})
        );
    }

    #[test]
    fn test_r_missing() {
        assert_match(
            vec![
                "ERROR: dependencies ‘ellipsis’, ‘pkgload’ are not available for package ‘testthat’"
            ],
            1,
            Some(MissingRPackage::simple("ellipsis")),
        );
        assert_match(
            vec!["  namespace ‘DBI’ 1.0.0 is being loaded, but >= 1.0.0.9003 is required"],
            1,
            Some(MissingRPackage {
                package: "DBI".to_owned(),
                minimum_version: Some("1.0.0.9003".to_owned()),
            }),
        );
        assert_match(
            vec![
                "  namespace ‘spatstat.utils’ 1.13-0 is already loaded, but >= 1.15.0 is required",
            ],
            1,
            Some(MissingRPackage {
                package: "spatstat.utils".to_owned(),
                minimum_version: Some("1.15.0".to_owned()),
            }),
        );
        assert_match(
            vec!["Error in library(zeligverse) : there is no package called 'zeligverse'"],
            1,
            Some(MissingRPackage::simple("zeligverse")),
        );
        assert_match(
            vec!["there is no package called 'mockr'"],
            1,
            Some(MissingRPackage::simple("mockr")),
        );
        assert_match(
            vec![
                "ERROR: dependencies 'igraph', 'matlab', 'expm', 'RcppParallel' are not available for package 'markovchain'"
            ],
            1,
            Some(MissingRPackage::simple("igraph"))
        );
        assert_match(
            vec![
                "Error: package 'BH' 1.66.0-1 was found, but >= 1.75.0.0 is required by 'RSQLite'",
            ],
            1,
            Some(MissingRPackage {
                package: "BH".to_owned(),
                minimum_version: Some("1.75.0.0".to_owned()),
            }),
        );
        assert_match(
         vec![
                "Error: package ‘AnnotationDbi’ 1.52.0 was found, but >= 1.53.1 is required by ‘GO.db’"
            ],
            1,
            Some(MissingRPackage{ package: "AnnotationDbi".to_owned(), minimum_version: Some("1.53.1".to_owned())})
        );
        assert_match(
            vec!["  namespace 'alakazam' 1.1.0 is being loaded, but >= 1.1.0.999 is required"],
            1,
            Some(MissingRPackage {
                package: "alakazam".to_string(),
                minimum_version: Some("1.1.0.999".to_string()),
            }),
        );
    }

    #[test]
    fn test_mv_stat() {
        assert_match(
            vec!["mv: cannot stat '/usr/res/boss.png': No such file or directory"],
            1,
            Some(MissingFile::new("/usr/res/boss.png".into())),
        );
        assert_just_match(
            vec!["mv: cannot stat 'res/boss.png': No such file or directory"],
            1,
        );
    }

    #[test]
    fn test_dh_link_error() {
        assert_match(
            vec![
                "dh_link: link destination debian/r-cran-crosstalk/usr/lib/R/site-library/crosstalk/lib/ionrangeslider is a directory"
            ],
            1,
            Some(DhLinkDestinationIsDirectory(
                "debian/r-cran-crosstalk/usr/lib/R/site-library/crosstalk/lib/ionrangeslider".to_owned()
            )),
        );
    }

    #[test]
    fn test_go_test() {
        assert_just_match(vec!["FAIL\tgithub.com/edsrzf/mmap-go\t0.083s"], 1);
    }

    #[test]
    fn test_debhelper_pattern() {
        assert_match(
            vec![
                r#"dh_install: Cannot find (any matches for) "server/etc/gnumed/gnumed-restore.conf" (tried in ., debian/tmp)"#,
            ],
            1,
            Some(DebhelperPatternNotFound {
                pattern: "server/etc/gnumed/gnumed-restore.conf".to_owned(),
                tool: "install".to_owned(),
                directories: vec![".".to_string(), "debian/tmp".to_string()],
            }),
        );
    }

    #[test]
    fn test_symbols() {
        assert_match(
            vec![
                "dpkg-gensymbols: error: some symbols or patterns disappeared in the symbols file: see diff output below"
            ],
            1,
            Some(DisappearedSymbols)
        );
    }

    #[test]
    fn test_missing_php_class() {
        assert_match(
            vec![
                "PHP Fatal error:  Uncaught Error: Class 'PHPUnit_Framework_TestCase' not found in /tmp/autopkgtest.gO7h1t/build.b1p/src/Horde_Text_Diff-2.2.0/test/Horde/Text/Diff/EngineTest.php:9"
            ],
            1,
            Some(MissingPhpClass{php_class: "PHPUnit_Framework_TestCase".to_owned()})
        );
    }

    #[test]
    fn test_missing_java_class() {
        assert_match(
            r#"Caused by: java.lang.ClassNotFoundException: org.codehaus.Xpp3r$Builder
\tat org.codehaus.strategy.SelfFirstStrategy.loadClass(lfFirstStrategy.java:50)
\tat org.codehaus.realm.ClassRealm.unsynchronizedLoadClass(ClassRealm.java:271)
\tat org.codehaus.realm.ClassRealm.loadClass(ClassRealm.java:247)
\tat org.codehaus.realm.ClassRealm.loadClass(ClassRealm.java:239)
\t... 46 more
"#
            .split("\n")
            .collect::<Vec<&str>>(),
            1,
            Some(MissingJavaClass {
                classname: "org.codehaus.Xpp3r$Builder".to_owned(),
            }),
        );
    }

    #[test]
    fn test_install_docs_link() {
        assert_just_match(
            r#"dh_installdocs: --link-doc not allowed between sympow and sympow-data (one is \
arch:all and the other not)"#
                .split("\n")
                .collect::<Vec<&str>>(),
            1,
        );
    }

    #[test]
    fn test_dh_until_unsupported() {
        assert_match(
            vec![
                "dh: The --until option is not supported any longer (#932537). Use override targets instead."
            ],
            1,
            Some(DhUntilUnsupported)
        );
    }

    #[test]
    fn test_missing_xml_entity() {
        assert_match(
            vec![
                "I/O error : Attempt to load network entity http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd"
            ],
            1,
            Some(MissingXmlEntity{url: "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd".to_owned()})
        );
    }

    #[test]
    fn test_ccache_error() {
        assert_match(
            vec![
                "ccache: error: Failed to create directory /sbuild-nonexistent/.ccache/tmp: Permission denied"
            ],
            1,
            Some(CcacheError(
                "Failed to create directory /sbuild-nonexistent/.ccache/tmp: Permission denied".to_owned()
            ))
        );
    }

    #[test]
    fn test_dh_addon_load_failure() {
        assert_match(
            vec![
                "dh: unable to load addon nodejs: Debian/Debhelper/Sequence/nodejs.pm did not return a true value at (eval 11) line 1."
            ],
            1,
            Some(DhAddonLoadFailure{name: "nodejs".to_owned(), path: "Debian/Debhelper/Sequence/nodejs.pm".to_owned()})
        );
    }

    #[test]
    fn test_missing_library() {
        assert_match(
            vec!["/usr/bin/ld: cannot find -lpthreads"],
            1,
            Some(MissingLibrary("pthreads".to_owned())),
        );
        assert_just_match(
            vec!["./testFortranCompiler.f:4: undefined reference to `sgemm_'"],
            1,
        );
        assert_just_match(
            vec!["writer.d:59: error: undefined reference to 'sam_hdr_parse_'"],
            1,
        );
    }

    #[test]
    fn test_assembler() {
        assert_match(vec!["Found no assembler"], 1, Some(MissingAssembler))
    }

    #[test]
    fn test_command_missing() {
        assert_match(
            vec!["./ylwrap: line 176: yacc: command not found"],
            1,
            Some(MissingCommand("yacc".to_owned())),
        );
        assert_match(
            vec!["/bin/sh: 1: cmake: not found"],
            1,
            Some(MissingCommand("cmake".to_owned())),
        );
        assert_match(
            vec!["sh: 1: git: not found"],
            1,
            Some(MissingCommand("git".to_owned())),
        );
        assert_match(
            vec!["/usr/bin/env: ‘python3’: No such file or directory"],
            1,
            Some(MissingCommand("python3".to_owned())),
        );
        assert_match(
            vec!["%Error: 'flex' must be installed to build"],
            1,
            Some(MissingCommand("flex".to_owned())),
        );
        assert_match(
            vec![r#"pkg-config: exec: "pkg-config": executable file not found in $PATH"#],
            1,
            Some(MissingCommand("pkg-config".to_owned())),
        );
        assert_match(
            vec![r#"Can't exec "git": No such file or directory at Makefile.PL line 25."#],
            1,
            Some(MissingCommand("git".to_owned())),
        );
        assert_match(
            vec![
                "vcver.scm.git.GitCommandError: 'git describe --tags --match 'v*' --abbrev=0' returned an error code 127"
            ],
            1,
            Some(MissingCommand("git".to_owned())),
        );
        assert_match(
            vec!["make[1]: docker: Command not found"],
            1,
            Some(MissingCommand("docker".to_owned())),
        );
        assert_match(
            vec!["make[1]: git: Command not found"],
            1,
            Some(MissingCommand("git".to_owned())),
        );
        assert_just_match(vec!["make[1]: ./docker: Command not found"], 1);
        assert_match(
            vec!["make: dh_elpa: Command not found"],
            1,
            Some(MissingCommand("dh_elpa".to_owned())),
        );
        assert_match(
            vec!["/bin/bash: valac: command not found"],
            1,
            Some(MissingCommand("valac".to_owned())),
        );
        assert_match(
            vec!["E: Failed to execute “python3”: No such file or directory"],
            1,
            Some(MissingCommand("python3".to_owned())),
        );
        assert_match(
            vec![
                r#"Can't exec "cmake": No such file or directory at /usr/share/perl5/Debian/Debhelper/Dh_Lib.pm line 484."#,
            ],
            1,
            Some(MissingCommand("cmake".to_owned())),
        );
        assert_match(
            vec!["Invalid gemspec in [unicorn.gemspec]: No such file or directory - git"],
            1,
            Some(MissingCommand("git".to_owned())),
        );
        assert_match(
            vec!["dbus-run-session: failed to exec 'xvfb-run': No such file or directory"],
            1,
            Some(MissingCommand("xvfb-run".to_owned())),
        );
        assert_match(
            vec!["/bin/sh: 1: ./configure: not found"],
            1,
            Some(MissingConfigure),
        );
        assert_match(
            vec!["xvfb-run: error: xauth command not found"],
            1,
            Some(MissingCommand("xauth".to_owned())),
        );
        assert_match(
            vec!["meson.build:39:2: ERROR: Program(s) ['wrc'] not found or not executable"],
            1,
            Some(MissingCommand("wrc".to_owned())),
        );
        assert_match(
            vec![
                "/tmp/autopkgtest.FnbV06/build.18W/src/debian/tests/blas-testsuite: 7: dpkg-architecture: not found"
            ],
            1,
            Some(MissingCommand("dpkg-architecture".to_owned())),
        );
        assert_match(
            vec![
                "Traceback (most recent call last):",
                r#"  File "/usr/lib/python3/dist-packages/mesonbuild/mesonmain.py", line 140, in run"#,
                "    return options.run_func(options)",
                r#"  File "/usr/lib/python3/dist-packages/mesonbuild/mdist.py", line 267, in run"#,
                "    names = create_dist_git(dist_name, archives, src_root, bld_root, dist_sub, b.dist_scripts, subprojects)",
                r#"  File "/usr/lib/python3/dist-packages/mesonbuild/mdist.py", line 119, in create_dist_git"#,
                "    git_clone(src_root, distdir)",
                r#"  File "/usr/lib/python3/dist-packages/mesonbuild/mdist.py", line 108, in git_clone"#,
                "    if git_have_dirty_index(src_root):",
                r#"  File "/usr/lib/python3/dist-packages/mesonbuild/mdist.py", line 104, in git_have_dirty_index"#,
                "    ret = subprocess.call(['git', '-C', src_root, 'diff-index', '--quiet', 'HEAD'])",
                r#"  File "/usr/lib/python3.9/subprocess.py", line 349, in call"#,
                "    with Popen(*popenargs, **kwargs) as p:",
                r#"  File "/usr/lib/python3.9/subprocess.py", line 951, in __init__"#,
                "    self._execute_child(args, executable, preexec_fn, close_fds,",
                r#"  File "/usr/lib/python3.9/subprocess.py", line 1823, in _execute_child"#,
                "    raise child_exception_type(errno_num, err_msg, err_filename)",
                "FileNotFoundError: [Errno 2] No such file or directory: 'git'",
            ],
            18,
            Some(MissingCommand("git".to_owned())),
        );
        assert_match(
            vec![r#"> Cannot run program "git": error=2, No such file or directory"#],
            1,
            Some(MissingCommand("git".to_owned())),
        );
        assert_match(
            vec!["E ImportError: Bad git executable"],
            1,
            Some(MissingCommand("git".to_owned())),
        );
        assert_match(
            vec!["E ImportError: Bad git executable."],
            1,
            Some(MissingCommand("git".to_owned())),
        );
        assert_match(
            vec![r#"Could not find external command "java""#],
            1,
            Some(MissingCommand("java".to_owned())),
        );
    }

    #[test]
    fn test_ts_error() {
        assert_just_match(
            vec!["blah/tokenizer.ts(175,21): error TS2532: Object is possibly 'undefined'."],
            1,
        );
    }

    #[test]
    fn test_pkg_config_missing() {
        assert_match(
            vec!["configure: error: Package requirements (apertium-3.2 >= 3.2.0) were not met:"],
            1,
            Some(MissingPkgConfig::new(
                "apertium-3.2".to_owned(),
                Some("3.2.0".to_owned()),
            )),
        );
        assert_match(
            vec![
                "checking for GLEW... configure: error: Package requirements (glew) were not met:",
            ],
            1,
            Some(MissingPkgConfig::simple("glew".to_owned())),
        );
        assert_match(
            vec!["meson.build:10:0: ERROR: Dependency \"gssdp-1.2\" not found, tried pkgconfig"],
            1,
            Some(MissingPkgConfig::simple("gssdp-1.2".to_owned())),
        );
        assert_match(
            vec![
                "src/plugins/sysprof/meson.build:3:0: ERROR: Dependency \"sysprof-3\" not found, tried pkgconfig"
            ],
            1,
            Some(MissingPkgConfig::simple("sysprof-3".to_owned())),
        );
        assert_match(
            vec![
                "meson.build:84:0: ERROR: Invalid version of dependency, need 'libpeas-1.0' ['>= 1.24.0'] found '1.22.0'."
            ],
            1,
            Some(MissingPkgConfig::new("libpeas-1.0".to_owned(), Some("1.24.0".to_owned()))),
        );
        assert_match(
            vec![
                "meson.build:233:0: ERROR: Invalid version of dependency, need 'vte-2.91' ['>=0.63.0'] found '0.62.3'."
            ],
            1,
            Some(MissingPkgConfig::new("vte-2.91".to_owned(), Some("0.63.0".to_owned()))),
        );

        assert_match(
            vec!["No package 'tepl-3' found"],
            1,
            Some(MissingPkgConfig::simple("tepl-3".to_owned())),
        );
        assert_match(
            vec!["Requested 'vte-2.91 >= 0.59.0' but version of vte is 0.58.2"],
            1,
            Some(MissingPkgConfig::new(
                "vte-2.91".to_owned(),
                Some("0.59.0".to_owned()),
            )),
        );
        assert_match(
            vec!["configure: error: x86_64-linux-gnu-pkg-config sdl2 couldn't be found"],
            1,
            Some(MissingPkgConfig::simple("sdl2".to_owned())),
        );
        assert_match(
            vec!["configure: error: No package 'libcrypto' found"],
            1,
            Some(MissingPkgConfig::simple("libcrypto".to_owned())),
        );
        assert_match(
            vec![
                "-- Checking for module 'gtk+-3.0'",
                "--   Package 'gtk+-3.0', required by 'virtual:world', not found",
            ],
            2,
            Some(MissingPkgConfig::simple("gtk+-3.0".to_owned())),
        );
        assert_match(
            vec![
                "configure: error: libfilezilla not found: Package dependency requirement 'libfilezilla >= 0.17.1' could not be satisfied."
            ],
            1,
            Some(MissingPkgConfig::new("libfilezilla".to_owned(), Some("0.17.1".to_owned()))),
        );
    }

    #[test]
    fn test_pkgconf() {
        assert_match(
            vec!["checking for LAPACK... configure: error: \"Cannot check for existence of module lapack without pkgconf\""],
            1,
            Some(MissingCommand("pkgconf".to_owned())),
        );
    }

    #[test]
    fn test_dh_with_order() {
        assert_match(
            vec!["dh: Unknown sequence --with (options should not come before the sequence)"],
            1,
            Some(DhWithOrderIncorrect),
        );
    }

    #[test]
    fn test_fpic() {
        assert_just_match(
            vec![
                "/usr/bin/ld: pcap-linux.o: relocation R_X86_64_PC32 against symbol `stderr@@GLIBC_2.2.5' can not be used when making a shared object; recompile with -fPIC"
            ],
            1,
        );
    }

    #[test]
    fn test_rspec() {
        assert_just_match(
            vec![
                "rspec ./spec/acceptance/cookbook_resource_spec.rb:20 # Client API operations downloading a cookbook when the cookbook of the name/version is found downloads the cookbook to the destination"
            ],
            1,
        );
    }

    #[test]
    fn test_multiple_definition() {
        assert_just_match(
            vec![
                "./dconf-paths.c:249: multiple definition of `dconf_is_rel_dir'; client/libdconf-client.a(dconf-paths.c.o):./obj-x86_64-linux-gnu/../common/dconf-paths.c:249: first defined here"
            ],
            1,
        );
        assert_just_match(
            vec![
                "/usr/bin/ld: ../lib/libaxe.a(stream.c.o):(.bss+0x10): multiple definition of `gsl_message_mask'; ../lib/libaxe.a(error.c.o):(.bss+0x8): first defined here"
            ],
            1,
        );
    }

    #[test]
    fn test_missing_ruby_gem() {
        assert_match(
            vec![
                "Could not find gem 'childprocess (~> 0.5)', which is required by gem 'selenium-webdriver', in any of the sources."
            ],
            1,
            Some(MissingRubyGem::new("childprocess".to_owned(), Some("0.5".to_owned()))),
        );
        assert_match(
            vec![
                "Could not find gem 'rexml', which is required by gem 'rubocop', in any of the sources."
            ],
            1,
            Some(MissingRubyGem::simple("rexml".to_owned())),
        );
        assert_match(
            vec![
                "/usr/lib/ruby/2.5.0/rubygems/dependency.rb:310:in `to_specs': Could not find 'http-parser' (~> 1.2.0) among 59 total gem(s) (Gem::MissingSpecError)"
            ],
            1,
            Some(MissingRubyGem::new("http-parser".to_owned(), Some("1.2.0".to_string()))),
        );
        assert_match(
            vec![
                "/usr/lib/ruby/2.5.0/rubygems/dependency.rb:312:in `to_specs': Could not find 'celluloid' (~> 0.17.3) - did find: [celluloid-0.16.0] (Gem::MissingSpecVersionError)"
            ],
            1,
            Some(MissingRubyGem{gem:"celluloid".to_owned(), version:Some("0.17.3".to_owned())}),
        );
        assert_match(
            vec![
                "/usr/lib/ruby/2.5.0/rubygems/dependency.rb:312:in `to_specs': Could not find 'i18n' (~> 0.7) - did find: [i18n-1.5.3] (Gem::MissingSpecVersionError)"
            ],
            1,
            Some(MissingRubyGem{gem:"i18n".to_owned(), version: Some("0.7".to_owned())}),
        );
        assert_match(
            vec![
                "/usr/lib/ruby/2.5.0/rubygems/dependency.rb:310:in `to_specs': Could not find 'sassc' (>= 2.0.0) among 34 total gem(s) (Gem::MissingSpecError)"
            ],
            1,
            Some(MissingRubyGem{gem:"sassc".to_string(), version: Some("2.0.0".to_string())}),
        );
        assert_match(
            vec![
                "/usr/lib/ruby/2.7.0/bundler/resolver.rb:290:in `block in verify_gemfile_dependencies_are_found!': Could not find gem 'rake-compiler' in any of the gem sources listed in your Gemfile. (Bundler::GemNotFound)"
            ],
            1,
            Some(MissingRubyGem::simple("rake-compiler".to_owned())),
        );
        assert_match(
            vec![
                "/usr/lib/ruby/2.7.0/rubygems.rb:275:in `find_spec_for_exe': can't find gem rdoc (>= 0.a) with executable rdoc (Gem::GemNotFoundException)"
            ],
            1,
            Some(MissingRubyGem::new("rdoc".to_owned(), Some("0.a".to_owned()))),
        );
    }

    #[test]
    fn test_missing_maven_artifacts() {
        assert_match(
            vec![
                "[ERROR] Failed to execute goal on project byteman-bmunit5: Could not resolve dependencies for project org.jboss.byteman:byteman-bmunit5:jar:4.0.7: The following artifacts could not be resolved: org.junit.jupiter:junit-jupiter-api:jar:5.4.0, org.junit.jupiter:junit-jupiter-params:jar:5.4.0, org.junit.jupiter:junit-jupiter-engine:jar:5.4.0: Cannot access central (https://repo.maven.apache.org/maven2) in offline mode and the artifact org.junit.jupiter:junit-jupiter-api:jar:5.4.0 has not been downloaded from it before. -> [Help 1]"
            ],
            1,
            Some(MissingMavenArtifacts(
                vec![
                    "org.junit.jupiter:junit-jupiter-api:jar:5.4.0".to_string(),
                    "org.junit.jupiter:junit-jupiter-params:jar:5.4.0".to_string(),
                    "org.junit.jupiter:junit-jupiter-engine:jar:5.4.0".to_string(),
                ]
            )),
        );
        assert_match(
            vec![
                "[ERROR] Failed to execute goal on project opennlp-uima: Could not resolve dependencies for project org.apache.opennlp:opennlp-uima:jar:1.9.2-SNAPSHOT: Cannot access ApacheIncubatorRepository (http://people.apache.org/repo/m2-incubating-repository/) in offline mode and the artifact org.apache.opennlp:opennlp-tools:jar:debian has not been downloaded from it before. -> [Help 1]"
            ],
            1,
            Some(MissingMavenArtifacts(vec!["org.apache.opennlp:opennlp-tools:jar:debian".to_string()])),
        );
        assert_match(
            vec![
                "[ERROR] Failed to execute goal on project bookkeeper-server: Could not resolve dependencies for project org.apache.bookkeeper:bookkeeper-server:jar:4.4.0: Cannot access central (https://repo.maven.apache.org/maven2) in offline mode and the artifact io.netty:netty:jar:debian has not been downloaded from it before. -> [Help 1]"
            ],
            1,
            Some(MissingMavenArtifacts(vec!["io.netty:netty:jar:debian".to_string()])),
        );
        assert_match(
            vec![
                "[ERROR] Unresolveable build extension: Plugin org.apache.felix:maven-bundle-plugin:2.3.7 or one of its dependencies could not be resolved: Cannot access central (https://repo.maven.apache.org/maven2) in offline mode and the artifact org.apache.felix:maven-bundle-plugin:jar:2.3.7 has not been downloaded from it before. @"
            ],
            1,
            Some(MissingMavenArtifacts(vec!["org.apache.felix:maven-bundle-plugin:2.3.7".to_string()])),
        );
        assert_match(
            vec![
                "[ERROR] Plugin org.apache.maven.plugins:maven-jar-plugin:2.6 or one of its dependencies could not be resolved: Cannot access central (https://repo.maven.apache.org/maven2) in offline mode and the artifact org.apache.maven.plugins:maven-jar-plugin:jar:2.6 has not been downloaded from it before. -> [Help 1]"
            ],
            1,
            Some(MissingMavenArtifacts(vec!["org.apache.maven.plugins:maven-jar-plugin:2.6".to_string()])),
        );

        assert_match(
            vec![
                "[FATAL] Non-resolvable parent POM for org.joda:joda-convert:2.2.1: Cannot access central (https://repo.maven.apache.org/maven2) in offline mode and the artifact org.joda:joda-parent:pom:1.4.0 has not been downloaded from it before. and 'parent.relativePath' points at wrong local POM @ line 8, column 10"],
            1,
            Some(MissingMavenArtifacts(vec!["org.joda:joda-parent:pom:1.4.0".to_string()])),
        );

        assert_match(
            vec![
                "[ivy:retrieve] \t\t:: com.carrotsearch.randomizedtesting#junit4-ant;${/com.carrotsearch.randomizedtesting/junit4-ant}: not found"
            ],
            1,
            Some(MissingMavenArtifacts(
                vec!["com.carrotsearch.randomizedtesting:junit4-ant:jar:debian".to_string()]
            )),
        );
        assert_match(
            vec![
                "[ERROR] Plugin org.apache.maven.plugins:maven-compiler-plugin:3.10.1 or one of its dependencies could not be resolved: Failed to read artifact descriptor for org.apache.maven.plugins:maven-compiler-plugin:jar:3.10.1: 1 problem was encountered while building the effective model for org.apache.maven.plugins:maven-compiler-plugin:3.10.1"
            ],
            1,
            Some(MissingMavenArtifacts(
                vec!["org.apache.maven.plugins:maven-compiler-plugin:3.10.1".to_string()]
            )),
        );
    }

    #[test]
    fn test_maven_errors() {
        assert_just_match(
            vec![
                "[ERROR] Failed to execute goal org.apache.maven.plugins:maven-jar-plugin:3.1.2:jar (default-jar) on project xslthl: Execution default-jar of goal org.apache.maven.plugins:maven-jar-plugin:3.1.2:jar failed: An API incompatibility was encountered while executing org.apache.maven.plugins:maven-jar-plugin:3.1.2:jar: java.lang.NoSuchMethodError: 'void org.codehaus.plexus.util.DirectoryScanner.setFilenameComparator(java.util.Comparator)'"],
            1,
        );
    }

    #[test]
    fn test_dh_missing_uninstalled() {
        assert_match(
            vec![
                "dh_missing --fail-missing", "dh_missing: usr/share/man/man1/florence_applet.1 exists in debian/tmp but is not installed to anywhere", "dh_missing: usr/lib/x86_64-linux-gnu/libflorence-1.0.la exists in debian/tmp but is not installed to anywhere", "dh_missing: missing files, aborting",
            ],
            3,
            Some(DhMissingUninstalled("usr/lib/x86_64-linux-gnu/libflorence-1.0.la".to_owned())),
        );
    }

    #[test]
    fn test_missing_perl_module() {
        assert_match(
            vec![
                "Converting tags.ledger... Can't locate String/Interpolate.pm in @INC (you may need to install the String::Interpolate module) (@INC contains: /etc/perl /usr/local/lib/x86_64-linux-gnu/perl/5.28.1 /usr/local/share/perl/5.28.1 /usr/lib/x86_64-linux-gnu/perl5/5.28 /usr/share/perl5 /usr/lib/x86_64-linux-gnu/perl/5.28 /usr/share/perl/5.28 /usr/local/lib/site_perl /usr/lib/x86_64-linux-gnu/perl-base) at ../bin/ledger2beancount line 23."
            ],
            1,
            Some(MissingPerlModule {
                filename: Some("String/Interpolate.pm".to_owned()),
                module: "String::Interpolate".to_owned(),
                inc: Some(vec![
                    "/etc/perl".to_owned(),
                    "/usr/local/lib/x86_64-linux-gnu/perl/5.28.1".to_owned(),
                    "/usr/local/share/perl/5.28.1".to_owned(),
                    "/usr/lib/x86_64-linux-gnu/perl5/5.28".to_owned(),
                    "/usr/share/perl5".to_owned(),
                    "/usr/lib/x86_64-linux-gnu/perl/5.28".to_owned(),
                    "/usr/share/perl/5.28".to_owned(),
                    "/usr/local/lib/site_perl".to_owned(),
                    "/usr/lib/x86_64-linux-gnu/perl-base".to_owned(),
                ]),
                minimum_version: None
            }),
        );
        assert_match(
            vec![
                "Can't locate Test/Needs.pm in @INC (you may need to install the Test::Needs module) (@INC contains: t/lib /<<PKGBUILDDIR>>/blib/lib /<<PKGBUILDDIR>>/blib/arch /etc/perl /usr/local/lib/x86_64-linux-gnu/perl/5.30.0 /usr/local/share/perl/5.30.0 /usr/lib/x86_64-linux-gnu/perl5/5.30 /usr/share/perl5 /usr/lib/x86_64-linux-gnu/perl/5.30 /usr/share/perl/5.30 /usr/local/lib/site_perl /usr/lib/x86_64-linux-gnu/perl-base .) at t/anon-basic.t line 7."
            ],
            1,
            Some(MissingPerlModule{
                filename: Some("Test/Needs.pm".to_owned()),
                module: "Test::Needs".to_owned(),
                inc: Some(vec![
                    "t/lib".to_owned(),
                    "/<<PKGBUILDDIR>>/blib/lib".to_owned(),
                    "/<<PKGBUILDDIR>>/blib/arch".to_owned(),
                    "/etc/perl".to_owned(),
                    "/usr/local/lib/x86_64-linux-gnu/perl/5.30.0".to_owned(),
                    "/usr/local/share/perl/5.30.0".to_owned(),
                    "/usr/lib/x86_64-linux-gnu/perl5/5.30".to_owned(),
                    "/usr/share/perl5".to_owned(),
                    "/usr/lib/x86_64-linux-gnu/perl/5.30".to_owned(),
                    "/usr/share/perl/5.30".to_owned(),
                    "/usr/local/lib/site_perl".to_owned(),
                    "/usr/lib/x86_64-linux-gnu/perl-base".to_owned(),
                    ".".to_owned(),
                ]),
                minimum_version: None
            }),
        );
        assert_match(
            vec!["- ExtUtils::Depends         ...missing. (would need 0.302)"],
            1,
            Some(MissingPerlModule {
                filename: None,
                module: "ExtUtils::Depends".to_owned(),
                inc: None,
                minimum_version: Some("0.302".to_owned()),
            }),
        );
        assert_match(
            vec![
                r#"Can't locate object method "new" via package "Dist::Inkt::Profile::TOBYINK" (perhaps you forgot to load "Dist::Inkt::Profile::TOBYINK"?) at /usr/share/perl5/Dist/Inkt.pm line 208."#,
            ],
            1,
            Some(MissingPerlModule::simple("Dist::Inkt::Profile::TOBYINK")),
        );
        assert_match(
            vec![
                "Can't locate ExtUtils/Depends.pm in @INC (you may need to install the ExtUtils::Depends module) (@INC contains: /etc/perl /usr/local/lib/x86_64-linux-gnu/perl/5.32.1 /usr/local/share/perl/5.32.1 /usr/lib/x86_64-linux-gnu/perl5/5.32 /usr/share/perl5 /usr/lib/x86_64-linux-gnu/perl-base /usr/lib/x86_64-linux-gnu/perl/5.32 /usr/share/perl/5.32 /usr/local/lib/site_perl) at (eval 11) line 1."
            ],
            1,
            Some(MissingPerlModule{
                filename: Some("ExtUtils/Depends.pm".to_owned()),
                module: "ExtUtils::Depends".to_owned(),
                inc: Some(vec![
                    "/etc/perl".to_owned(),
                    "/usr/local/lib/x86_64-linux-gnu/perl/5.32.1".to_owned(),
                    "/usr/local/share/perl/5.32.1".to_owned(),
                    "/usr/lib/x86_64-linux-gnu/perl5/5.32".to_owned(),
                    "/usr/share/perl5".to_owned(),
                    "/usr/lib/x86_64-linux-gnu/perl-base".to_owned(),
                    "/usr/lib/x86_64-linux-gnu/perl/5.32".to_owned(),
                    "/usr/share/perl/5.32".to_owned(),
                    "/usr/local/lib/site_perl".to_owned(),
                ]),
                minimum_version: None
            }),
        );
        assert_match(
            vec![
                "Pod::Weaver::Plugin::WikiDoc (for section -WikiDoc) does not appear to be installed"
            ],
            1,
            Some(MissingPerlModule::simple("Pod::Weaver::Plugin::WikiDoc")),
        );
        assert_match(
            vec![
                "List::Util version 1.56 required--this is only version 1.55 at /build/tmpttq5hhpt/package/blib/lib/List/AllUtils.pm line 8."
            ],
            1,
            Some(MissingPerlModule {
                filename: None,
                inc: None,
                module: "List::Util".to_owned(), minimum_version: Some("1.56".to_owned())}),
        );
    }

    #[test]
    fn test_missing_perl_file() {
        assert_match(
            vec![
                "Can't locate debian/perldl.conf in @INC (@INC contains: /<<PKGBUILDDIR>>/inc /etc/perl /usr/local/lib/x86_64-linux-gnu/perl/5.28.1 /usr/local/share/perl/5.28.1 /usr/lib/x86_64-linux-gnu/perl5/5.28 /usr/share/perl5 /usr/lib/x86_64-linux-gnu/perl/5.28 /usr/share/perl/5.28 /usr/local/lib/site_perl /usr/lib/x86_64-linux-gnu/perl-base) at Makefile.PL line 131."
            ],
            1,
            Some(MissingPerlFile {
                filename: "debian/perldl.conf".to_owned(),
                inc: Some(vec![
                    "/<<PKGBUILDDIR>>/inc".to_owned(),
                    "/etc/perl".to_owned(),
                    "/usr/local/lib/x86_64-linux-gnu/perl/5.28.1".to_owned(),
                    "/usr/local/share/perl/5.28.1".to_owned(),
                    "/usr/lib/x86_64-linux-gnu/perl5/5.28".to_owned(),
                    "/usr/share/perl5".to_owned(),
                    "/usr/lib/x86_64-linux-gnu/perl/5.28".to_owned(),
                    "/usr/share/perl/5.28".to_owned(),
                    "/usr/local/lib/site_perl".to_owned(),
                    "/usr/lib/x86_64-linux-gnu/perl-base".to_owned(),
                ]),
            }),
        );
        assert_match(
            vec![r#"Can't open perl script "Makefile.PL": No such file or directory"#],
            1,
            Some(MissingPerlFile {
                filename: "Makefile.PL".to_owned(),
                inc: None,
            }),
        );
    }

    #[test]
    fn test_perl_expand() {
        assert_match(
            vec![">(error): Could not expand [ 'Dist::Inkt::Profile::TOBYINK'"],
            1,
            Some(MissingPerlModule::simple("Dist::Inkt::Profile::TOBYINK")),
        );
    }

    #[test]
    fn test_perl_missing_predeclared() {
        assert_match(
            vec![
                "String found where operator expected at Makefile.PL line 13, near \"author_tests 'xt'\"", "\t(Do you need to predeclare author_tests?)",
                "syntax error at Makefile.PL line 13, near \"author_tests 'xt'\"", r#""strict subs" in use at Makefile.PL line 13."#,
            ],
            2,
            Some(MissingPerlPredeclared("author_tests".to_owned())),
        );
        assert_match(
            vec![
                "String found where operator expected at Makefile.PL line 8, near \"readme_from    'lib/URL/Encode.pod'\""
            ],
            1,
            Some(MissingPerlPredeclared("readme_from".to_owned())),
        );

        assert_match(
            vec![
                r#"Bareword "use_test_base" not allowed while "strict subs" in use at Makefile.PL line 12."#,
            ],
            1,
            Some(MissingPerlPredeclared("use_test_base".to_owned())),
        );
    }

    #[test]
    fn test_unknown_cert_authority() {
        assert_match(
            vec![
                r#"go: github.com/golangci/golangci-lint@v1.24.0: Get "https://proxy.golang.org/github.com/golangci/golangci-lint/@v/v1.24.0.mod": x509: certificate signed by unknown authority"#,
            ],
            1,
            Some(UnknownCertificateAuthority(
                "https://proxy.golang.org/github.com/golangci/golangci-lint/@v/v1.24.0.mod"
                    .to_owned(),
            )),
        );
    }

    #[test]
    fn test_no_disk_space() {
        assert_match(
            vec![
                "/usr/bin/install: error writing '/<<PKGBUILDDIR>>/debian/tmp/usr/lib/gcc/x86_64-linux-gnu/8/cc1objplus': No space left on device"
            ],
            1,
            Some(NoSpaceOnDevice)
        );

        assert_match(
            ["OSError: [Errno 28] No space left on device"].to_vec(),
            1,
            Some(NoSpaceOnDevice),
        );
    }

    #[test]
    fn test_segmentation_fault() {
        assert_just_match(
            vec![
                r#"/bin/bash: line 3:  7392 Segmentation fault      itstool -m "${mo}" ${d}/C/index.docbook ${d}/C/legal.xml"#,
            ],
            1,
        );
    }

    #[test]
    fn test_missing_perl_plugin() {
        assert_match(
            vec!["Required plugin bundle Dist::Zilla::PluginBundle::Git isn't installed."],
            1,
            Some(MissingPerlModule::simple("Dist::Zilla::PluginBundle::Git")),
        );
        assert_match(
            vec!["Required plugin Dist::Zilla::Plugin::PPPort isn't installed."],
            1,
            Some(MissingPerlModule::simple("Dist::Zilla::Plugin::PPPort")),
        );
    }

    #[test]
    fn test_nim_error() {
        assert_just_match(
            vec![
                "/<<PKGBUILDDIR>>/msgpack4nim.nim(470, 6) Error: usage of 'isNil' is a user-defined error",
            ],
            1,
        );
    }

    #[test]
    fn test_scala_error() {
        assert_just_match(
            vec![
                "core/src/main/scala/org/json4s/JsonFormat.scala:131: error: No JSON deserializer found for type List[T]. Try to implement an implicit Reader or JsonFormat for this type."
            ],
            1,
        );
    }

    #[test]
    fn test_vala_error() {
        assert_just_match(
vec![
                "../src/Backend/FeedServer.vala:60.98-60.148: error: The name `COLLECTION_CREATE_NONE' does not exist in the context of `Secret.CollectionCreateFlags'"
            ],
            1,
        );
        assert_match(
            vec![
                "error: Package `glib-2.0' not found in specified Vala API directories or GObject-Introspection GIR directories"
            ],
            1,
            Some(MissingValaPackage("glib-2.0".to_owned())),
        );
    }

    #[test]
    fn test_gir() {
        assert_match(
            vec!["ValueError: Namespace GnomeDesktop not available"],
            1,
            Some(MissingIntrospectionTypelib("GnomeDesktop".to_owned())),
        );
    }

    #[test]
    fn test_missing_boost_components() {
        assert_match(
            r#"""CMake Error at /usr/share/cmake-3.18/Modules/FindPackageHandleStandardArgs.cmake:165 (message):
  Could NOT find Boost (missing: program_options filesystem system graph
  serialization iostreams) (found suitable version "1.74.0", minimum required
  is "1.55.0")
Call Stack (most recent call first):
  /usr/share/cmake-3.18/Modules/FindPackageHandleStandardArgs.cmake:458 (_FPHSA_FAILURE_MESSAGE)
  /usr/share/cmake-3.18/Modules/FindBoost.cmake:2177 (find_package_handle_standard_args)
  src/CMakeLists.txt:4 (find_package)
"""#.split_inclusive('\n').collect::<Vec<&str>>(),
            4,
            Some(MissingCMakeComponents{
                name: "Boost".to_owned(),
                components: vec![
                    "program_options".to_owned(),
                    "filesystem".to_owned(),
                    "system".to_owned(),
                    "graph".to_owned(),
                    "serialization".to_owned(),
                    "iostreams".to_owned(),
                ],
            }),
        );
    }

    #[test]
    fn test_pkg_config_too_old() {
        assert_match(
            vec![
                "checking for pkg-config... no",
                "",
                "*** Your version of pkg-config is too old. You need atleast",
                "*** pkg-config 0.9.0 or newer. You can download pkg-config",
                "*** from the freedesktop.org software repository at",
                "***",
                "***    https://www.freedesktop.org/wiki/Software/pkg-config/",
                "***",
            ],
            4,
            Some(MissingVagueDependency {
                name: "pkg-config".to_owned(),
                minimum_version: Some("0.9.0".to_owned()),
                url: None,
                current_version: None,
            }),
        );
    }

    #[test]
    fn test_missing_jdk() {
        assert_match(
            vec![
                "> Kotlin could not find the required JDK tools in the Java installation '/usr/lib/jvm/java-8-openjdk-amd64/jre' used by Gradle. Make sure Gradle is running on a JDK, not JRE.",
            ],
            1,
            Some(MissingJDK::new("/usr/lib/jvm/java-8-openjdk-amd64/jre".to_owned())),
        );
    }

    #[test]
    fn test_missing_jre() {
        assert_match(
            vec!["ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH."],
            1,
            Some(MissingJRE),
        );
    }

    #[test]
    fn test_node_module_missing() {
        assert_match(
            vec!["Error: Cannot find module 'tape'"],
            1,
            Some(MissingNodeModule("tape".to_owned())),
        );
        assert_just_match(
            vec!["✖ [31mERROR:[39m Cannot find module '/<<PKGBUILDDIR>>/test'"],
            1,
        );
        assert_match(
            vec!["npm ERR! [!] Error: Cannot find module '@rollup/plugin-buble'"],
            1,
            Some(MissingNodeModule("@rollup/plugin-buble".to_owned())),
        );
        assert_match(
            vec!["npm ERR! Error: Cannot find module 'fs-extra'"],
            1,
            Some(MissingNodeModule("fs-extra".to_owned())),
        );
        assert_match(
            vec!["\x1b[1m\x1b[31m[!] \x1b[1mError: Cannot find module '@rollup/plugin-buble'"],
            1,
            Some(MissingNodeModule("@rollup/plugin-buble".to_owned())),
        );
    }

    #[test]
    fn test_setup_py_command() {
        assert_match(
            r#"""/usr/lib/python3.9/distutils/dist.py:274: UserWarning: Unknown distribution option: 'long_description_content_type'
  warnings.warn(msg)
/usr/lib/python3.9/distutils/dist.py:274: UserWarning: Unknown distribution option: 'test_suite'
  warnings.warn(msg)
/usr/lib/python3.9/distutils/dist.py:274: UserWarning: Unknown distribution option: 'python_requires'
  warnings.warn(msg)
usage: setup.py [global_opts] cmd1 [cmd1_opts] [cmd2 [cmd2_opts] ...]
   or: setup.py --help [cmd1 cmd2 ...]
   or: setup.py --help-commands
   or: setup.py cmd --help

error: invalid command 'test'
"""#.split_inclusive('\n').collect::<Vec<&str>>(),
            12,
            Some(MissingSetupPyCommand("test".to_owned())),
        );
    }

    #[test]
    fn test_c_header_missing() {
        assert_match(
            vec!["cdhit-common.h:39:9: fatal error: zlib.h: No such file or directory"],
            1,
            Some(MissingCHeader {
                header: "zlib.h".to_owned(),
            }),
        );
        assert_match(
            vec![
                "/<<PKGBUILDDIR>>/Kernel/Operation_Vector.cpp:15:10: fatal error: petscvec.h: No such file or directory"
            ],
            1,
            Some(MissingCHeader{header: "petscvec.h".to_owned()}),
        );
        assert_match(
            vec!["src/bubble.h:27:10: fatal error: DBlurEffectWidget: No such file or directory"],
            1,
            Some(MissingCHeader {
                header: "DBlurEffectWidget".to_owned(),
            }),
        );
    }

    #[test]
    fn test_missing_jdk_file() {
        assert_match(
            vec![
                "> Could not find tools.jar. Please check that /usr/lib/jvm/java-8-openjdk-amd64 contains a valid JDK installation.",
            ],
            1,
            Some(MissingJDKFile{jdk_path: "/usr/lib/jvm/java-8-openjdk-amd64".to_owned(), filename: "tools.jar".to_owned()}),
        );
    }

    #[test]
    fn test_python2_import() {
        assert_match(
            vec!["ImportError: No module named pytz"],
            1,
            Some(MissingPythonModule::simple("pytz".to_owned())),
        );
        assert_just_match(vec!["ImportError: cannot import name SubfieldBase"], 1);
    }

    #[test]
    fn test_python3_import() {
        assert_match(
            ["ModuleNotFoundError: No module named 'django_crispy_forms'"].to_vec(),
            1,
            Some(MissingPythonModule {
                module: "django_crispy_forms".to_owned(),
                python_version: Some(3),
                minimum_version: None,
            }),
        );
        assert_match(
            [" ModuleNotFoundError: No module named 'Cython'"].to_vec(),
            1,
            Some(MissingPythonModule {
                module: "Cython".to_owned(),
                python_version: Some(3),
                minimum_version: None,
            }),
        );
        assert_match(
            ["ModuleNotFoundError: No module named 'distro'"].to_vec(),
            1,
            Some(MissingPythonModule {
                module: "distro".to_owned(),
                python_version: Some(3),
                minimum_version: None,
            }),
        );
        assert_match(
            ["E   ModuleNotFoundError: No module named 'twisted'"].to_vec(),
            1,
            Some(MissingPythonModule {
                module: "twisted".to_owned(),
                python_version: Some(3),
                minimum_version: None,
            }),
        );
        assert_match(
            vec![
                "E   ImportError: cannot import name 'async_poller' from 'msrest.polling' (/usr/lib/python3/dist-packages/msrest/polling/__init__.py)"
            ],
            1,
            Some(MissingPythonModule::simple("msrest.polling.async_poller".to_owned())),
        );
        assert_match(
            vec!["/usr/bin/python3: No module named sphinx"],
            1,
            Some(MissingPythonModule {
                module: "sphinx".to_owned(),
                python_version: Some(3),
                minimum_version: None,
            }),
        );
        assert_match(
            vec![
                "Could not import extension sphinx.ext.pngmath (exception: No module named pngmath)"
            ],
            1,
            Some(MissingPythonModule::simple("pngmath".to_owned())),
        );
        assert_match(
            vec![
                "/usr/bin/python3: Error while finding module specification for 'pep517.build' (ModuleNotFoundError: No module named 'pep517')"
            ],
            1,
            Some(MissingPythonModule{module: "pep517".to_owned(), python_version:Some(3), minimum_version: None}),
        );
    }

    #[test]
    fn test_sphinx() {
        assert_just_match(
            vec!["There is a syntax error in your configuration file: Unknown syntax: Constant"],
            1,
        );
    }

    #[test]
    fn test_go_missing() {
        assert_match(
            vec![
                r#"src/github.com/vuls/config/config.go:30:2: cannot find package "golang.org/x/xerrors" in any of:"#,
            ],
            1,
            Some(MissingGoPackage {
                package: "golang.org/x/xerrors".to_owned(),
            }),
        );
    }

    #[test]
    fn test_lazy_font() {
        assert_match(
            vec![
                "[ERROR] LazyFont - Failed to read font file /usr/share/texlive/texmf-dist/fonts/opentype/public/stix2-otf/STIX2Math.otf <java.io.FileNotFoundException: /usr/share/texlive/texmf-dist/fonts/opentype/public/stix2-otf/STIX2Math.otf (No such file or directory)>java.io.FileNotFoundException: /usr/share/texlive/texmf-dist/fonts/opentype/public/stix2-otf/STIX2Math.otf (No such file or directory)"],
            1,
            Some(MissingFile::new(
                "/usr/share/texlive/texmf-dist/fonts/opentype/public/stix2-otf/STIX2Math.otf".into()
            )),
        );
    }

    #[test]
    fn test_missing_latex_files() {
        assert_match(
            vec!["! LaTeX Error: File `fancyvrb.sty' not found."],
            1,
            Some(MissingLatexFile("fancyvrb.sty".to_owned())),
        );
    }

    #[test]
    fn test_pytest_import() {
        assert_match(
            vec!["E   ImportError: cannot import name cmod"],
            1,
            Some(MissingPythonModule::simple("cmod".to_owned())),
        );
        assert_match(
            vec!["E   ImportError: No module named mock"],
            1,
            Some(MissingPythonModule::simple("mock".to_owned())),
        );
        assert_match(
            vec![
                "pluggy.manager.PluginValidationError: Plugin 'xdist.looponfail' could not be loaded: (pytest 3.10.1 (/usr/lib/python2.7/dist-packages), Requirement.parse('pytest>=4.4.0'))!"
            ],
            1,
            Some(MissingPythonModule{
                module: "pytest".to_owned(), python_version: Some(2), minimum_version: Some("4.4.0".to_owned())
            }),
        );
        assert_match(
            vec![
                r#"ImportError: Error importing plugin "tests.plugins.mock_libudev": No module named mock"#,
            ],
            1,
            Some(MissingPythonModule::simple("mock".to_owned())),
        );
    }

    #[test]
    fn test_sed() {
        assert_match(
            vec!["sed: can't read /etc/locale.gen: No such file or directory"],
            1,
            Some(MissingFile::new("/etc/locale.gen".into())),
        );
    }

    #[test]
    fn test_pytest_args() {
        assert_match(
            vec![
                "pytest: error: unrecognized arguments: --cov=janitor --cov-report=html --cov-report=term-missing:skip-covered"
            ],
            1,
            Some(UnsupportedPytestArguments(
                vec![
                    "--cov=janitor".to_owned(),
                    "--cov-report=html".to_owned(),
                    "--cov-report=term-missing:skip-covered".to_owned(),
                ]
            )),
        );
    }

    #[test]
    fn test_pytest_config() {
        assert_match(
            vec!["INTERNALERROR> pytest.PytestConfigWarning: Unknown config option: asyncio_mode"],
            1,
            Some(UnsupportedPytestConfigOption("asyncio_mode".to_owned())),
        );
    }

    #[test]
    fn test_distutils_missing() {
        assert_match(
            vec![
                "distutils.errors.DistutilsError: Could not find suitable distribution for Requirement.parse('pytest-runner')"
            ],
            1,
            Some(MissingPythonDistribution::simple("pytest-runner")),
        );
        assert_match(
            vec![
                "distutils.errors.DistutilsError: Could not find suitable distribution for Requirement.parse('certifi>=2019.3.9')"
            ],
            1,
            Some(MissingPythonDistribution{distribution: "certifi".to_owned(), minimum_version: Some("2019.3.9".to_owned()), python_version: None }),
        );
        assert_match(
            vec![
                r#"distutils.errors.DistutilsError: Could not find suitable distribution for Requirement.parse('cffi; platform_python_implementation == "CPython"\')"#,
            ],
            1,
            Some(MissingPythonDistribution::simple("cffi")),
        );
        assert_match(
            vec!["error: Could not find suitable distribution for Requirement.parse('gitlab')"],
            1,
            Some(MissingPythonDistribution::simple("gitlab")),
        );
        assert_match(
            vec![
                "pkg_resources.DistributionNotFound: The 'configparser>=3.5' distribution was not found and is required by importlib-metadata"
            ],
            1,
            Some(MissingPythonDistribution{distribution:"configparser".to_owned(), minimum_version: Some("3.5".to_owned()), python_version: None}),
        );
        assert_match(
            vec![
                "error: Command '['/usr/bin/python3.9', '-m', 'pip', '--disable-pip-version-check', 'wheel', '--no-deps', '-w', '/tmp/tmp973_8lhm', '--quiet', 'asynctest']' returned non-zero exit status 1."
            ],
            1,
            Some(MissingPythonDistribution{distribution: "asynctest".to_owned(), python_version:Some(3), minimum_version: None}),
        );
        assert_match(
            vec![
                "subprocess.CalledProcessError: Command '['/usr/bin/python', '-m', 'pip', '--disable-pip-version-check', 'wheel', '--no-deps', '-w', '/tmp/tmpm2l3kcgv', '--quiet', 'setuptools_scm']' returned non-zero exit status 1."
            ],
            1,
            Some(MissingPythonDistribution::simple("setuptools-scm")),
        );
    }

    #[test]
    fn test_cmake_missing_file() {
        assert_match(
            r#"""CMake Error at /usr/lib/x86_64-/cmake/Qt5Gui/Qt5GuiConfig.cmake:27 (message):
  The imported target "Qt5::Gui" references the file

     "/usr/lib/x86_64-linux-gnu/libEGL.so"

  but this file does not exist.  Possible reasons include:

  * The file was deleted, renamed, or moved to another location.

  * An install or uninstall procedure did not complete successfully.

  * The installation package was faulty and contained

     "/usr/lib/x86_64-linux-gnu/cmake/Qt5Gui/Qt5GuiConfigExtras.cmake"

  but not all the files it references.

Call Stack (most recent call first):
  /usr/lib/x86_64-linux-gnu/QtGui/Qt5Gui.cmake:63 (_qt5_Gui_check_file_exists)
  /usr/lib/x86_64-linux-gnu/QtGui/Qt5Gui.cmake:85 (_qt5gui_find_extra_libs)
  /usr/lib/x86_64-linux-gnu/QtGui/Qt5Gui.cmake:186 (include)
  /usr/lib/x86_64-linux-gnu/QtWidgets/Qt5Widgets.cmake:101 (find_package)
  /usr/lib/x86_64-linux-gnu/Qt/Qt5Config.cmake:28 (find_package)
  CMakeLists.txt:34 (find_package)
dh_auto_configure: cd obj-x86_64-linux-gnu && cmake with args
"""#
            .split_inclusive('\n')
            .collect::<Vec<&str>>(),
            16,
            Some(MissingFile::new(
                "/usr/lib/x86_64-linux-gnu/libEGL.so".into(),
            )),
        );
    }

    #[test]
    fn test_cmake_missing_include() {
        assert_match(
            r#"""-- Performing Test _OFFT_IS_64BIT
-- Performing Test _OFFT_IS_64BIT - Success
-- Performing Test HAVE_DATE_TIME
-- Performing Test HAVE_DATE_TIME - Success
CMake Error at CMakeLists.txt:43 (include):
  include could not find load file:

    KDEGitCommitHooks


-- Found KF5Activities: /usr/lib/x86_64-linux-gnu/cmake/KF5Activities/KF5ActivitiesConfig.cmake (found version "5.78.0") 
-- Found KF5Config: /usr/lib/x86_64-linux-gnu/cmake/KF5Config/KF5ConfigConfig.cmake (found version "5.78.0") 
"""#.split_inclusive('\n').collect::<Vec<&str>>(),
            8,
            Some(CMakeFilesMissing{filenames:vec!["KDEGitCommitHooks.cmake".to_string()], version :None}),
        );
    }

    #[test]
    fn test_cmake_missing_cmake_files() {
        assert_match(
            r#"""CMake Error at /usr/share/cmake-3.22/Modules/FindPackageHandleStandardArgs.cmake:230 (message):
  Could not find a package configuration file provided by "sensor_msgs" with
  any of the following names:

    sensor_msgsConfig.cmake
    sensor_msgs-config.cmake

  Add the installation prefix of "sensor_msgs" to CMAKE_PREFIX_PATH or set
  "sensor_msgs_DIR" to a directory containing one of the above files.  If
  "sensor_msgs" provides a separate development package or SDK, be sure it
  has been installed.
dh_auto_configure: cd obj-x86_64-linux-gnu && cmake with args
"""#
            .split_inclusive('\n')
            .collect::<Vec<&str>>(),
            11,
            Some(CMakeFilesMissing {
                filenames: vec![
                    "sensor_msgsConfig.cmake".to_string(),
                    "sensor_msgs-config.cmake".to_string(),
                ],
                version: None,
            }),
        );
        assert_match(
            r#"""CMake Error at /usr/share/cmake-3.22/Modules/FindPackageHandleStandardArgs.cmake:230 (message):
  Could NOT find KF5 (missing: Plasma PlasmaQuick Wayland ModemManagerQt
  NetworkManagerQt) (found suitable version "5.92.0", minimum required is
  "5.86")
"""#.split_inclusive('\n').collect::<Vec<&str>>(),
            4,
            Some(MissingCMakeComponents{
                name: "KF5".into(),
                components: vec![
                    "Plasma".into(),
                    "PlasmaQuick".into(),
                    "Wayland".into(),
                    "ModemManagerQt".into(),
                    "NetworkManagerQt".into(),
                ],
            }),
        );
    }

    #[test]
    fn test_cmake_missing_exact_version() {
        assert_match(
            r#"""CMake Error at /usr/share/cmake-3.18/Modules/FindPackageHandleStandardArgs.cmake:165 (message):
  Could NOT find SignalProtocol: Found unsuitable version "2.3.3", but
  required is exact version "2.3.2" (found
  /usr/lib/x86_64-linux-gnu/libsignal-protocol-c.so)
"""#.split_inclusive('\n').collect::<Vec<&str>>(),
            4,
            Some(CMakeNeedExactVersion{
                package: "SignalProtocol".to_owned(),
                version_found: "2.3.3".to_owned(),
                exact_version_needed: "2.3.2".to_owned(),
                path: "/usr/lib/x86_64-linux-gnu/libsignal-protocol-c.so".into(),
            }),
        );
    }

    #[test]
    fn test_cmake_missing_vague() {
        assert_match(
            vec![
                "CMake Error at CMakeLists.txt:84 (MESSAGE):",
                "  alut not found",
            ],
            2,
            Some(MissingVagueDependency::simple("alut")),
        );
        assert_match(
            vec![
                "CMake Error at CMakeLists.txt:213 (message):",
                "  could not find zlib",
            ],
            2,
            Some(MissingVagueDependency::simple("zlib")),
        );
        assert_match(
            r#"""-- Found LibSolv_ext: /usr/lib/x86_64-linux-gnu/libsolvext.so  
-- Found LibSolv: /usr/include /usr/lib/x86_64-linux-gnu/libsolv.so;/usr/lib/x86_64-linux-gnu/libsolvext.so
-- No usable gpgme flavours found.
CMake Error at cmake/modules/FindGpgme.cmake:398 (message):
  Did not find GPGME
Call Stack (most recent call first):
  CMakeLists.txt:223 (FIND_PACKAGE)
  """#.split_inclusive('\n').collect::<Vec<&str>>(),
            5,
            Some(MissingVagueDependency::simple("GPGME")),
        );
    }

    #[test]
    fn test_secondary() {
        assert!(super::find_secondary_build_failure(&["Unknown option --foo"], 10).is_some());
        assert!(
            super::find_secondary_build_failure(&["Unknown option --foo, ignoring."], 10).is_none()
        );
    }

    #[test]
    fn test_missing_haskell_dependencies_trait() {
        let problem = MissingHaskellDependencies(vec!["base".to_string(), "text".to_string()]);
        assert_eq!(problem.kind(), "missing-haskell-dependencies");
        let json = problem.json();
        assert_eq!(json["deps"], serde_json::json!(["base", "text"]));
    }

    #[test]
    fn test_missing_ruby_file_trait() {
        let problem = MissingRubyFile {
            filename: "foo.rb".to_string(),
        };
        let json = problem.json();
        assert_eq!(json["filename"], "foo.rb");
    }

    #[test]
    fn test_missing_configure_trait() {
        let problem = MissingConfigure;
        assert_eq!(problem.kind(), "missing-configure");
    }

    #[test]
    fn test_missing_latex_file_trait() {
        let problem = MissingLatexFile("article.cls".to_string());
        assert_eq!(problem.kind(), "missing-latex-file");
    }

    #[test]
    fn test_debhelper_pattern_not_found_trait() {
        let problem = DebhelperPatternNotFound {
            pattern: "debian/tmp/usr/lib/*".to_string(),
            tool: "dh_install".to_string(),
            directories: vec![],
        };
        assert_eq!(problem.kind(), "debhelper-pattern-not-found");
    }

    #[test]
    fn test_vcs_control_directory_needed_trait() {
        let problem = VcsControlDirectoryNeeded {
            vcs: vec![".git".to_string()],
        };
        assert_eq!(problem.kind(), "vcs-control-directory-needed");
    }

    #[test]
    fn test_missing_ocaml_package_trait() {
        let problem = MissingOCamlPackage("lwt".to_string());
        let json = problem.json();
        assert_eq!(json["package"], "lwt");
    }

    #[test]
    fn test_missing_python_module_display() {
        let problem = MissingPythonModule {
            module: "numpy".to_string(),
            python_version: Some(3),
            minimum_version: None,
        };
        let display = format!("{}", problem);
        assert!(display.contains("numpy"));
    }

    #[test]
    fn test_missing_go_runtime_trait() {
        let problem = MissingGoRuntime;
        assert_eq!(problem.kind(), "missing-go-runtime");
    }

    #[test]
    fn test_unknown_certificate_authority_trait() {
        let problem = UnknownCertificateAuthority("https://example.com".to_string());
        assert_eq!(problem.kind(), "unknown-certificate-authority");
    }

    #[test]
    fn test_missing_command_or_build_file_trait() {
        let problem = MissingCommandOrBuildFile {
            filename: "make".to_string(),
        };
        assert_eq!(problem.kind(), "missing-command-or-build-file");
    }

    #[test]
    fn test_missing_php_class_trait() {
        let problem = MissingPhpClass::new("PHPUnit\\Framework\\TestCase".to_string());
        assert_eq!(problem.kind(), "missing-php-class");
    }

    #[test]
    fn test_missing_go_mod_file_display() {
        let problem = MissingGoModFile;
        let display = format!("{}", problem);
        assert_eq!(display, "go.mod file is missing");
    }

    #[test]
    fn test_backtrack_limit_handling() {
        // Test that find_secondary_build_failure handles long lines gracefully
        // without panicking on BacktrackLimitExceeded errors

        // Create a line that could cause backtracking issues with some regex patterns
        let long_line = format!("error: {}", "a".repeat(5000));
        let lines = vec![long_line.as_str()];

        // This should not panic and should return gracefully
        let _result = find_secondary_build_failure(&lines, 1);
        // The result could be Some or None, but it shouldn't panic

        // Test that normal error lines still work
        let normal_line = "Unknown option --foo";
        let lines = vec![normal_line];
        let result = find_secondary_build_failure(&lines, 1);
        assert!(result.is_some());
    }
}
