summaryrefslogtreecommitdiffstats
path: root/src/commands/util.rs
blob: 674258d46d9a048b54f827159a2207bb6051ff4c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
use std::path::Path;

use anyhow::Result;
use anyhow::anyhow;
use clap::ArgMatches;
use log::{error, info, trace};
use tokio::stream::StreamExt;

use crate::config::*;
use crate::package::Package;
use crate::package::PhaseName;
use crate::package::ScriptBuilder;
use crate::package::Shebang;

pub fn getbool(m: &ArgMatches, name: &str, cmp: &str) -> bool {
    // unwrap is safe here because clap is configured with default values
    m.values_of(name).unwrap().any(|v| v == cmp)
}

pub async fn lint_packages<'a, I>(iter: I, linter: &Path, config: &Configuration, bar: indicatif::ProgressBar) -> Result<()>
    where I: Iterator<Item = &'a Package> + 'a
{
    let shebang = Shebang::from(config.shebang().clone());
    let lint_results = iter
        .map(|pkg| {
            let shebang = shebang.clone();
            let bar = bar.clone();
            async move {
                trace!("Linting script of {} {} with '{}'", pkg.name(), pkg.version(), linter.display());
                let _ = all_phases_available(pkg, config.available_phases())?;

                let cmd = tokio::process::Command::new(linter);
                let script = ScriptBuilder::new(&shebang)
                    .build(pkg, config.available_phases(), *config.strict_script_interpolation())?;

                let (status, stdout, stderr) = script.lint(cmd).await?;
                bar.inc(1);
                Ok((pkg.name().clone(), pkg.version().clone(), status, stdout, stderr))
            }
        })
        .collect::<futures::stream::FuturesUnordered<_>>()
        .collect::<Result<Vec<_>>>()
        .await?
        .into_iter()
        .map(|tpl| {
            let pkg_name = tpl.0;
            let pkg_vers = tpl.1;
            let status = tpl.2;
            let stdout = tpl.3;
            let stderr = tpl.4;

            if status.success() {
                info!("Linting {pkg_name} {pkg_vers} script (exit {status}):\nstdout:\n{stdout}\n\nstderr:\n\n{stderr}",
                    pkg_name = pkg_name,
                    pkg_vers = pkg_vers,
                    status = status,
                    stdout = stdout,
                    stderr = stderr
                );
                true
            } else {
                error!("Linting {pkg_name} {pkg_vers} errored ({status}):\n\nstdout:\n{stdout}\n\nstderr:\n{stderr}\n\n",
                    pkg_name = pkg_name,
                    pkg_vers = pkg_vers,
                    status = status,
                    stdout = stdout,
                    stderr = stderr
                );
                false
            }
        })
        .collect::<Vec<_>>();

    let lint_ok = lint_results.iter().all(|b| *b);

    if !lint_ok {
        bar.finish_with_message("Linting errored");
        return Err(anyhow!("Linting was not successful"))
    } else {
        bar.finish_with_message(&format!("Finished linting {} package scripts", lint_results.len()));
        Ok(())
    }
}

fn all_phases_available(pkg: &Package, available_phases: &Vec<PhaseName>) -> Result<()> {
    let package_phasenames = pkg.phases().keys().collect::<Vec<_>>();

    if let Some(phase) = package_phasenames.iter().filter(|name| !available_phases.contains(name)).next() {
        return Err(anyhow!("Phase '{}' available in {} {}, but not in config", phase.as_str(), pkg.name(), pkg.version()))
    }

    if let Some(phase) = available_phases.iter().filter(|name| !package_phasenames.contains(name)).next() {
        return Err(anyhow!("Phase '{}' not configured in {} {}", phase.as_str(), pkg.name(), pkg.version()))
    }

    Ok(())
}