char/avril

(wip) why'd you have to go and make things so complicaaaaated?

git clone https://git.t4t.associates/char/avril

Charlotte Somdeslop(task): unify watchset + glob machinerya29d09f

main
4.1 KiB139 linesraw
1use std::collections::BTreeMap;
2use std::path::PathBuf;
3
4use serde::{Deserialize, Serialize};
5use sha2::{Digest, Sha256};
6
7use crate::{Error, Result};
8
9/// A stable identity for a guest function used as a task action. `id` is
10/// meaningful only to the driver that registered the function; `key` is a
11/// content identity (e.g. a hash of the function source) for fingerprinting.
12#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
13pub struct CallbackRef {
14    pub id: u32,
15    pub key: String,
16}
17
18#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
19pub enum Action {
20    None,
21    /// Run through the platform shell (`sh -c` / `cmd /C`).
22    Shell(String),
23    /// Spawn directly, without a shell.
24    Argv(Vec<String>),
25    /// Invoke a guest function; only drivers with a guest runtime can do this.
26    Callback(CallbackRef),
27}
28
29impl Action {
30    pub fn kind(&self) -> &'static str {
31        match self {
32            Action::None => "none",
33            Action::Shell(_) => "shell",
34            Action::Argv(_) => "argv",
35            Action::Callback(_) => "callback",
36        }
37    }
38    /// The text used for fingerprinting. Distinct kinds can never collide:
39    /// only shell commands use the bare string form, for compatibility with
40    /// fingerprints recorded by earlier avril versions.
41    pub(crate) fn fingerprint_text(&self) -> String {
42        match self {
43            Action::None => String::new(),
44            Action::Shell(command) => command.clone(),
45            Action::Argv(argv) => format!("argv\0{}", serde_json::to_string(argv).unwrap()),
46            Action::Callback(callback) => format!("callback\0{}", callback.key),
47        }
48    }
49    pub fn display(&self) -> String {
50        match self {
51            Action::None => String::new(),
52            Action::Shell(command) => command.clone(),
53            Action::Argv(argv) => argv
54                .iter()
55                .map(|a| shell_quote(a))
56                .collect::<Vec<_>>()
57                .join(" "),
58            Action::Callback(_) => "<function>".into(),
59        }
60    }
61}
62
63#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
64pub struct Task {
65    pub description: Option<String>,
66    pub action: Action,
67    pub dependencies: Vec<String>,
68    pub parallel: bool,
69    pub env_file: Option<PathBuf>,
70    /// Declaring `files` opts the task into result caching.
71    pub files: Option<Vec<String>>,
72    pub output: Vec<String>,
73    pub env: Vec<String>,
74}
75
76impl Default for Task {
77    fn default() -> Self {
78        Task {
79            description: None,
80            action: Action::None,
81            dependencies: Vec::new(),
82            parallel: false,
83            env_file: None,
84            files: None,
85            output: Vec::new(),
86            env: Vec::new(),
87        }
88    }
89}
90
91#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
92pub struct Graph {
93    pub tasks: BTreeMap<String, Task>,
94}
95
96impl Graph {
97    pub fn get(&self, name: &str) -> Result<&Task> {
98        self.tasks
99            .get(name)
100            .ok_or_else(|| Error::UnknownTask(name.to_owned()))
101    }
102
103    /// A content digest of the whole graph, for drivers that need to know
104    /// whether a re-evaluated graph differs from the previous one.
105    pub fn digest(&self) -> String {
106        let bytes = serde_json::to_vec(self).unwrap();
107        format!("{:x}", Sha256::digest(&bytes))
108    }
109}
110
111#[cfg(not(windows))]
112pub fn shell_quote(arg: &str) -> String {
113    if !arg.is_empty()
114        && arg.bytes().all(|ch| {
115            ch.is_ascii_alphanumeric()
116                || matches!(
117                    ch,
118                    b'-' | b'_' | b'.' | b'/' | b':' | b'@' | b'%' | b'+' | b'=' | b','
119                )
120        })
121    {
122        arg.to_owned()
123    } else {
124        format!("'{}'", arg.replace('\'', "'\\''"))
125    }
126}
127
128#[cfg(windows)]
129pub fn shell_quote(arg: &str) -> String {
130    if !arg.is_empty()
131        && arg.bytes().all(|ch| {
132            ch.is_ascii_alphanumeric() || matches!(ch, b'-' | b'_' | b'.' | b'/' | b':' | b'@')
133        })
134    {
135        arg.to_owned()
136    } else {
137        format!("\"{}\"", arg.replace('"', "\\\""))
138    }
139}