1pub fn job_name_slug(name: &str) -> String {
2 let mut slug = String::new();
3 for ch in name.chars() {
4 if ch.is_ascii_alphanumeric() {
5 slug.push(ch.to_ascii_lowercase());
6 } else {
7 match ch {
8 ' ' | '-' | '_' => slug.push('-'),
9 _ => continue,
10 }
11 }
12 }
13
14 if slug.is_empty() {
15 slug.push_str("job");
16 }
17
18 slug
19}
20
21pub fn stage_name_slug(name: &str) -> String {
22 job_name_slug(name)
23}
24
25pub fn project_slug(path: &str) -> String {
26 let mut slug = String::new();
27 for ch in path.chars() {
28 match ch {
29 'a'..='z' | '0'..='9' => slug.push(ch),
30 'A'..='Z' => slug.push(ch.to_ascii_lowercase()),
31 '/' | '_' | '-' => slug.push('-'),
32 _ => continue,
33 }
34 }
35 if slug.is_empty() {
36 slug.push_str("project");
37 }
38 slug
39}
40
41pub fn escape_double_quotes(input: &str) -> String {
42 let mut escaped = String::with_capacity(input.len());
43 for ch in input.chars() {
44 match ch {
45 '"' => escaped.push_str("\\\""),
46 '\\' => escaped.push_str("\\\\"),
47 _ => escaped.push(ch),
48 }
49 }
50 escaped
51}
52
53use crate::ExecutorConfig;
54use sha2::{Digest, Sha256};
55use std::process;
56use std::time::{SystemTime, UNIX_EPOCH};
57
58pub fn generate_run_id(config: &ExecutorConfig) -> String {
59 let pipeline_slug = config
60 .pipeline
61 .file_stem()
62 .and_then(|stem| stem.to_str())
63 .map(job_name_slug)
64 .unwrap_or_else(|| "pipeline".to_string());
65 let nanos = SystemTime::now()
66 .duration_since(UNIX_EPOCH)
67 .map(|d| d.as_nanos())
68 .unwrap_or(0);
69
70 let mut hasher = Sha256::new();
71 hasher.update(pipeline_slug.as_bytes());
72 hasher.update(nanos.to_le_bytes());
73 hasher.update(process::id().to_le_bytes());
74
75 let digest = hasher.finalize();
76 let suffix = format!("{:x}", digest);
77 let short = &suffix[..8];
78 format!("{pipeline_slug}-{short}")
79}