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
99
100
101
102
//! Subprocess handling is platform specific code.
//!
//! The submodules of this module represent the different implementations for
//! each supported platform.
//! Depending on the target, the respective platform is read and loaded into this scope.

use std::{collections::HashMap, process::Command};

use crate::{network::message::Signal as InternalSignal, settings::Settings};

// Unix specific process handling
// Shared between Linux and Apple
#[cfg(unix)]
mod unix;
#[cfg(unix)]
pub use self::unix::*;
#[cfg(unix)]
use command_group::Signal;

// Platform specific process support
#[cfg_attr(target_os = "linux", path = "linux.rs")]
#[cfg_attr(target_vendor = "apple", path = "apple.rs")]
#[cfg_attr(target_os = "windows", path = "windows.rs")]
#[cfg_attr(target_os = "freebsd", path = "freebsd.rs")]
mod platform;
pub use self::platform::*;

/// Pueue directly interacts with processes.
/// Since these interactions can vary depending on the current platform, this enum is introduced.
/// The intend is to keep any platform specific code out of the top level code.
/// Even if that implicates adding some layers of abstraction.
#[derive(Debug)]
pub enum ProcessAction {
    Pause,
    Resume,
}

impl From<&ProcessAction> for Signal {
    fn from(action: &ProcessAction) -> Self {
        match action {
            ProcessAction::Pause => Signal::SIGSTOP,
            ProcessAction::Resume => Signal::SIGCONT,
        }
    }
}

impl From<InternalSignal> for Signal {
    fn from(signal: InternalSignal) -> Self {
        match signal {
            InternalSignal::SigKill => Signal::SIGKILL,
            InternalSignal::SigInt => Signal::SIGINT,
            InternalSignal::SigTerm => Signal::SIGTERM,
            InternalSignal::SigCont => Signal::SIGCONT,
            InternalSignal::SigStop => Signal::SIGSTOP,
        }
    }
}

/// Take a platform specific shell command and insert the actual task command via templating.
pub fn compile_shell_command(settings: &Settings, command: &str) -> Command {
    let shell_command = get_shell_command(settings);

    let mut handlebars = handlebars::Handlebars::new();
    handlebars.set_strict_mode(true);
    handlebars.register_escape_fn(handlebars::no_escape);

    // Make the command available to the template engine.
    let mut parameters = HashMap::new();
    parameters.insert("pueue_command_string", command);

    // We allow users to provide their own shell command.
    // They should use the `{{ pueue_command_string }}` placeholder.
    let mut compiled_command = Vec::new();
    for part in shell_command {
        let compiled_part = handlebars
            .render_template(&part, &parameters)
            .unwrap_or_else(|_| {
                panic!("Failed to render shell command for template: {part} and parameters: {parameters:?}")
            });

        compiled_command.push(compiled_part);
    }

    let executable = compiled_command.remove(0);

    // Chain two `powershell` commands, one that sets the output encoding to utf8 and then the user provided one.
    let mut command = Command::new(executable);
    for arg in compiled_command {
        command.arg(&arg);
    }

    // Inject custom environment variables.
    if !settings.daemon.env_vars.is_empty() {
        log::info!(
            "Inject environment variables: {:?}",
            &settings.daemon.env_vars
        );
        command.envs(&settings.daemon.env_vars);
    }

    command
}