1use std::{borrow::Cow, fs, path::Path, process};
2
3use blue_build_recipe::{MaybeVersion, Recipe};
4use blue_build_utils::constants::{
5 CONFIG_PATH, CONTAINER_FILE, CONTAINERFILES_PATH, COSIGN_PUB_PATH, FILES_PATH,
6};
7use bon::Builder;
8use chrono::Utc;
9use colored::control::ShouldColorize;
10use log::{debug, error, trace, warn};
11use uuid::Uuid;
12
13pub use askama::Template;
14
15#[derive(Debug, Clone, Template, Builder)]
16#[template(path = "Containerfile.j2", escape = "none", whitespace = "minimize")]
17#[builder(on(Cow<'_, str>, into))]
18pub struct ContainerFileTemplate<'a> {
19 #[builder(into)]
20 recipe: &'a Recipe<'a>,
21
22 #[builder(into)]
23 recipe_path: Cow<'a, Path>,
24
25 #[builder(into)]
26 build_id: Uuid,
27 os_version: u64,
28 registry: Cow<'a, str>,
29 build_scripts_image: Cow<'a, str>,
30 repo: Cow<'a, str>,
31 base_digest: Cow<'a, str>,
32 nushell_version: Option<&'a MaybeVersion>,
33}
34
35impl ContainerFileTemplate<'_> {
36 const fn should_install_nu(&self) -> bool {
37 match self.nushell_version {
38 None | Some(MaybeVersion::Version(_)) => true,
39 Some(MaybeVersion::None) => false,
40 }
41 }
42
43 fn get_nu_version(&self) -> String {
44 match self.nushell_version {
45 Some(MaybeVersion::None) | None => "default".to_string(),
46 Some(MaybeVersion::Version(version)) => version.to_string(),
47 }
48 }
49}
50
51#[derive(Debug, Clone, Template, Builder)]
52#[template(path = "github_issue.j2", escape = "md")]
53#[builder(on(Cow<'_, str>, into))]
54pub struct GithubIssueTemplate<'a> {
55 bb_version: Cow<'a, str>,
56 build_rust_channel: Cow<'a, str>,
57 build_time: Cow<'a, str>,
58 git_commit_hash: Cow<'a, str>,
59 os_name: Cow<'a, str>,
60 os_version: Cow<'a, str>,
61 pkg_branch_tag: Cow<'a, str>,
62 recipe: Cow<'a, str>,
63 rust_channel: Cow<'a, str>,
64 rust_version: Cow<'a, str>,
65 shell_name: Cow<'a, str>,
66 shell_version: Cow<'a, str>,
67 terminal_name: Cow<'a, str>,
68 terminal_version: Cow<'a, str>,
69}
70
71#[derive(Debug, Clone, Template, Builder)]
72#[template(path = "init/README.j2", escape = "md")]
73#[builder(on(Cow<'_, str>, into))]
74pub struct InitReadmeTemplate<'a> {
75 repo_name: Cow<'a, str>,
76 registry: Cow<'a, str>,
77 image_name: Cow<'a, str>,
78}
79
80#[derive(Debug, Clone, Template, Builder)]
81#[template(path = "init/gitlab-ci.yml.j2", escape = "none")]
82#[builder(on(Cow<'_, str>, into))]
83pub struct GitlabCiTemplate<'a> {
84 version: Cow<'a, str>,
85}
86
87fn has_cosign_file() -> bool {
88 trace!("has_cosign_file()");
89 std::env::current_dir()
90 .map(|p| p.join(COSIGN_PUB_PATH).exists())
91 .unwrap_or(false)
92}
93
94#[must_use]
95fn print_containerfile(containerfile: &str) -> String {
96 trace!("print_containerfile({containerfile})");
97 debug!("Loading containerfile contents for {containerfile}");
98
99 let legacy_path = Path::new(CONFIG_PATH);
100 let containerfiles_path = Path::new(CONTAINERFILES_PATH);
101
102 let path = if containerfiles_path.exists() && containerfiles_path.is_dir() {
103 containerfiles_path.join(format!("{containerfile}/{CONTAINER_FILE}"))
104 } else {
105 warn!(
106 "Use of {CONFIG_PATH} is deprecated for the containerfile module, please move your containerfile directories into {CONTAINERFILES_PATH}"
107 );
108 legacy_path.join(format!("containerfiles/{containerfile}/{CONTAINER_FILE}"))
109 };
110
111 let file = fs::read_to_string(&path).unwrap_or_else(|e| {
112 error!("Failed to read file {}: {e}", path.display());
113 process::exit(1);
114 });
115
116 debug!("Containerfile contents {}:\n{file}", path.display());
117
118 file
119}
120
121fn modules_exists() -> bool {
122 let mod_path = Path::new("modules");
123 mod_path.exists() && mod_path.is_dir()
124}
125
126fn files_dir_exists() -> bool {
127 let path = Path::new(FILES_PATH);
128 path.exists() && path.is_dir()
129}
130
131fn config_dir_exists() -> bool {
132 let path = Path::new(CONFIG_PATH);
133 let exists = path.exists() && path.is_dir();
134
135 if exists {
136 warn!(
137 "Use of the {CONFIG_PATH} directory is deprecated. Please move your non-recipe files into {FILES_PATH}"
138 );
139 }
140
141 exists
142}
143
144fn current_timestamp() -> String {
145 Utc::now().to_rfc3339()
146}
147
148fn should_color() -> bool {
149 ShouldColorize::from_env().should_colorize()
150}
151
152mod filters {
153 use askama::Values;
154
155 #[allow(clippy::unnecessary_wraps)]
156 pub fn replace<T>(input: T, _: &dyn Values, from: char, to: &str) -> askama::Result<String>
157 where
158 T: std::fmt::Display,
159 {
160 Ok(format!("{input}").replace(from, to))
161 }
162}