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

kaspar030 / laze / 13501209400

24 Feb 2025 03:08PM UTC coverage: 82.247% (-0.09%) from 82.337%
13501209400

Pull #652

github

web-flow
Merge bab295178 into ff8556d85
Pull Request #652: feat(task): Extend with working_directory option

13 of 19 new or added lines in 2 files covered. (68.42%)

11 existing lines in 1 file now uncovered.

3572 of 4343 relevant lines covered (82.25%)

104.3 hits per line

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

77.27
/src/model/task.rs
1
use std::path::Path;
2

3
use anyhow::{Error, Result};
4
use camino::Utf8Path;
5
use thiserror::Error;
6

7
use crate::nested_env;
8
use crate::serde_bool_helpers::{default_as_false, default_as_true};
9
use crate::EXIT_ON_SIGINT;
10

11
use super::shared::VarExportSpec;
12

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

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

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

41
    pub fn execute(
5✔
42
        &self,
5✔
43
        start_dir: &Path,
5✔
44
        args: Option<&Vec<&str>>,
5✔
45
        verbose: u8,
5✔
46
    ) -> Result<(), Error> {
5✔
47
        for cmd in &self.cmd {
13✔
48
            use shell_words::join;
5✔
49
            use std::process::Command;
5✔
50
            let mut command = Command::new("sh");
8✔
51

8✔
52
            let cmd = cmd.replace("$$", "$");
8✔
53
            if verbose > 0 {
8✔
UNCOV
54
                command.arg("-x");
×
55
            }
8✔
56

57
            if let Some(working_directory) = &self.working_directory {
8✔
NEW
58
                let working_directory = Utf8Path::new(working_directory);
×
NEW
59
                if working_directory.is_relative() {
×
NEW
60
                    command.current_dir(start_dir.join(working_directory));
×
NEW
61
                } else {
×
NEW
62
                    command.current_dir(working_directory);
×
NEW
63
                }
×
64
            } else {
8✔
65
                command.current_dir(start_dir);
8✔
66
            }
8✔
67

68
            command.arg("-c");
8✔
69

70
            // handle "export:" (export laze variables to task shell environment)
71
            if let Some(export) = &self.export {
8✔
72
                for entry in export {
15✔
73
                    let VarExportSpec { variable, content } = entry;
12✔
74
                    if let Some(val) = content {
12✔
75
                        command.env(variable, val);
12✔
76
                    }
12✔
77
                }
78
            }
5✔
79

80
            if let Some(args) = args {
8✔
81
                command.arg(cmd.clone() + " " + &join(args).to_owned());
8✔
82
            } else {
8✔
83
                command.arg(cmd);
×
84
            }
×
85

86
            if self.ignore_ctrl_c {
8✔
UNCOV
87
                EXIT_ON_SIGINT
×
UNCOV
88
                    .get()
×
UNCOV
89
                    .unwrap()
×
UNCOV
90
                    .clone()
×
UNCOV
91
                    .store(false, std::sync::atomic::Ordering::SeqCst);
×
92
            }
8✔
93

94
            // run command, wait for status
95
            let status = command.status().expect("executing command");
8✔
96

8✔
97
            if self.ignore_ctrl_c {
8✔
UNCOV
98
                EXIT_ON_SIGINT
×
UNCOV
99
                    .get()
×
UNCOV
100
                    .unwrap()
×
101
                    .clone()
×
UNCOV
102
                    .store(true, std::sync::atomic::Ordering::SeqCst);
×
103
            }
8✔
104

105
            if !status.success() {
8✔
UNCOV
106
                return Err(anyhow!("task failed"));
×
107
            }
8✔
108
        }
109
        Ok(())
5✔
110
    }
5✔
111

112
    fn _with_env(&self, env: &im::HashMap<&String, String>, do_eval: bool) -> Result<Task, Error> {
20✔
113
        let expand = |s| {
30✔
114
            if do_eval {
30✔
115
                nested_env::expand_eval(s, env, nested_env::IfMissing::Empty)
15✔
116
            } else {
117
                nested_env::expand(s, env, nested_env::IfMissing::Ignore)
15✔
118
            }
119
        };
30✔
120

121
        Ok(Task {
122
            cmd: self
20✔
123
                .cmd
20✔
124
                .iter()
20✔
125
                .map(expand)
20✔
126
                .collect::<Result<Vec<String>, _>>()?,
20✔
127
            export: if do_eval {
20✔
128
                self.expand_export(env)
10✔
129
            } else {
130
                self.export.clone()
10✔
131
            },
132
            working_directory: self.working_directory.as_ref().map(expand).transpose()?,
20✔
133
            ..(*self).clone()
20✔
134
        })
135
    }
20✔
136

137
    /// This is called early when loading the yaml files.
138
    /// It will not evaluate expressions, and pass-through variables that are not
139
    /// found in `env`.
140
    pub fn with_env(&self, env: &im::HashMap<&String, String>) -> Result<Task, Error> {
10✔
141
        self._with_env(env, false)
10✔
142
    }
10✔
143

144
    /// This is called to generate the final task.
145
    /// It will evaluate expressions, and variables that are not
146
    /// found in `env` will be replaced with the empty string.
147
    pub fn with_env_eval(&self, env: &im::HashMap<&String, String>) -> Result<Task, Error> {
10✔
148
        self._with_env(env, true)
10✔
149
    }
10✔
150

151
    fn expand_export(&self, env: &im::HashMap<&String, String>) -> Option<Vec<VarExportSpec>> {
10✔
152
        VarExportSpec::expand(self.export.as_ref(), env)
10✔
153
    }
10✔
154
}
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

© 2026 Coveralls, Inc