• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

kaspar030 / laze / 14430558848

13 Apr 2025 02:49PM UTC coverage: 80.865% (+0.003%) from 80.862%
14430558848

push

github

web-flow
feat(tasks): pass extra task args as shell args (#702)

7 of 10 new or added lines in 1 file covered. (70.0%)

3588 of 4437 relevant lines covered (80.87%)

102.7 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

73.47
/src/model/task.rs
1
use std::ffi::OsStr;
2
use std::path::Path;
3

4
use anyhow::{anyhow, Error, Result};
5
use itertools::Itertools;
6
use serde::{Deserialize, Serialize};
7
use thiserror::Error;
8

9
use crate::nested_env;
10
use crate::serde_bool_helpers::{default_as_false, default_as_true};
11
use crate::EXIT_ON_SIGINT;
12

13
use super::shared::VarExportSpec;
14

15
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
16
#[serde(deny_unknown_fields)]
17
pub struct Task {
18
    pub cmd: Vec<String>,
19
    pub help: Option<String>,
20
    pub required_vars: Option<Vec<String>>,
21
    pub required_modules: Option<Vec<String>>,
22
    pub export: Option<Vec<VarExportSpec>>,
23
    #[serde(default = "default_as_true")]
24
    pub build: bool,
25
    #[serde(default = "default_as_false")]
26
    pub ignore_ctrl_c: bool,
27
    pub workdir: Option<String>,
28
}
29

30
#[derive(Error, Debug, Serialize, Deserialize)]
31
pub enum TaskError {
32
    #[error("required variable `{var}` not set")]
33
    RequiredVarMissing { var: String },
34
    #[error("required module `{module}` not selected")]
35
    RequiredModuleMissing { module: String },
36
}
37

38
impl Task {
39
    pub fn build_app(&self) -> bool {
5✔
40
        self.build
5✔
41
    }
5✔
42

43
    pub fn execute(
5✔
44
        &self,
5✔
45
        start_dir: &Path,
5✔
46
        args: Option<&Vec<&str>>,
5✔
47
        verbose: u8,
5✔
48
    ) -> Result<(), Error> {
5✔
49
        if verbose > 0 {
5✔
NEW
50
            if let Some(args) = args {
×
NEW
51
                println!("laze: ... with args: {args:?}");
×
NEW
52
            }
×
53
        }
5✔
54

55
        for cmd in &self.cmd {
13✔
56
            use std::process::Command;
57

58
            let mut command = if cfg!(target_family = "windows") {
8✔
59
                let mut cmd = Command::new("cmd");
×
60
                cmd.arg("/C");
×
61
                cmd
×
62
            } else {
63
                let mut sh = Command::new("sh");
8✔
64
                sh.arg("-c");
8✔
65
                sh
8✔
66
            };
67

68
            let cmd = cmd.replace("$$", "$");
8✔
69

70
            if let Some(working_directory) = &self.workdir {
8✔
71
                // This includes support for absolute working directories through .join
×
72
                command.current_dir(start_dir.join(working_directory));
×
73
            } else {
8✔
74
                command.current_dir(start_dir);
8✔
75
            }
8✔
76

77
            // handle "export:" (export laze variables to task shell environment)
78
            if let Some(export) = &self.export {
8✔
79
                for entry in export {
15✔
80
                    let VarExportSpec { variable, content } = entry;
12✔
81
                    if let Some(val) = content {
12✔
82
                        command.env(variable, val);
12✔
83
                    }
12✔
84
                }
85
            }
5✔
86

87
            command.arg(cmd);
8✔
88

8✔
89
            if verbose > 0 {
8✔
90
                let command_with_args = command
×
91
                    .get_args()
×
92
                    .skip(1)
×
93
                    .map(OsStr::to_string_lossy)
×
94
                    .collect_vec();
×
95

×
96
                println!("laze: executing `{}`", command_with_args.join(" "));
×
97
            }
8✔
98

99
            if let Some(args) = args {
8✔
100
                command.arg("--");
8✔
101
                command.args(args);
8✔
102
            }
8✔
103

104
            if self.ignore_ctrl_c {
8✔
105
                EXIT_ON_SIGINT
×
106
                    .get()
×
107
                    .unwrap()
×
108
                    .clone()
×
109
                    .store(false, std::sync::atomic::Ordering::SeqCst);
×
110
            }
8✔
111

112
            // run command, wait for status
113
            let status = command.status().expect("executing command");
8✔
114

8✔
115
            if self.ignore_ctrl_c {
8✔
116
                EXIT_ON_SIGINT
×
117
                    .get()
×
118
                    .unwrap()
×
119
                    .clone()
×
120
                    .store(true, std::sync::atomic::Ordering::SeqCst);
×
121
            }
8✔
122

123
            if !status.success() {
8✔
124
                return Err(anyhow!("task failed"));
×
125
            }
8✔
126
        }
127
        Ok(())
5✔
128
    }
5✔
129

130
    fn _with_env(&self, env: &im::HashMap<&String, String>, do_eval: bool) -> Result<Task, Error> {
20✔
131
        let expand = |s| {
30✔
132
            if do_eval {
30✔
133
                nested_env::expand_eval(s, env, nested_env::IfMissing::Empty)
15✔
134
            } else {
135
                nested_env::expand(s, env, nested_env::IfMissing::Ignore)
15✔
136
            }
137
        };
30✔
138

139
        Ok(Task {
140
            cmd: self
20✔
141
                .cmd
20✔
142
                .iter()
20✔
143
                .map(expand)
20✔
144
                .collect::<Result<Vec<String>, _>>()?,
20✔
145
            export: if do_eval {
20✔
146
                self.expand_export(env)
10✔
147
            } else {
148
                self.export.clone()
10✔
149
            },
150
            workdir: self.workdir.as_ref().map(expand).transpose()?,
20✔
151
            ..(*self).clone()
20✔
152
        })
153
    }
20✔
154

155
    /// This is called early when loading the yaml files.
156
    /// It will not evaluate expressions, and pass-through variables that are not
157
    /// found in `env`.
158
    pub fn with_env(&self, env: &im::HashMap<&String, String>) -> Result<Task, Error> {
10✔
159
        self._with_env(env, false)
10✔
160
    }
10✔
161

162
    /// This is called to generate the final task.
163
    /// It will evaluate expressions, and variables that are not
164
    /// found in `env` will be replaced with the empty string.
165
    pub fn with_env_eval(&self, env: &im::HashMap<&String, String>) -> Result<Task, Error> {
10✔
166
        self._with_env(env, true)
10✔
167
    }
10✔
168

169
    fn expand_export(&self, env: &im::HashMap<&String, String>) -> Option<Vec<VarExportSpec>> {
10✔
170
        VarExportSpec::expand(self.export.as_ref(), env)
10✔
171
    }
10✔
172
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc