use std::collections::HashMap; use crate::features::FeatureLayer; /// Generate a combined Dockerfile from base + features + env + user. pub fn generate( base_dockerfile: &str, feature_layers: &[FeatureLayer], container_env: &HashMap, remote_user: Option<&str>, ) -> String { let mut sections: Vec = Vec::new(); sections.push("# Generated by arc-devcontainer".to_string()); sections.push(base_dockerfile.to_string()); for layer in feature_layers { sections.push(layer.dockerfile_snippet.clone()); } if !container_env.is_empty() { let mut keys: Vec<&String> = container_env.keys().collect(); keys.sort(); let env_lines: Vec = keys .iter() .map(|k| format!("ENV {}={}", k, container_env[*k])) .collect(); sections.push(env_lines.join("\n")); } if let Some(user) = remote_user { sections.push(format!("USER {}", user)); } let mut result = sections.join("\n\n"); result.push('\n'); result } #[cfg(test)] mod tests { use super::*; fn make_layer(id: &str, dir_name: &str, snippet: &str) -> FeatureLayer { FeatureLayer { id: id.to_string(), dir_name: dir_name.to_string(), dockerfile_snippet: snippet.to_string(), } } #[test] fn base_image_only() { let result = generate("FROM ubuntu:22.04", &[], &HashMap::new(), None); insta::assert_snapshot!(result, @r" # Generated by arc-devcontainer FROM ubuntu:22.04 "); } #[test] fn base_dockerfile_preserved_as_is() { let base = "FROM ubuntu:22.04\nRUN apt-get update\nRUN apt-get install -y curl"; let result = generate(base, &[], &HashMap::new(), None); insta::assert_snapshot!(result, @r" # Generated by arc-devcontainer FROM ubuntu:22.04 RUN apt-get update RUN apt-get install -y curl "); } #[test] fn with_feature_layers() { let layers = vec![ make_layer("node", "node-1", "RUN install-node.sh"), make_layer("python", "python-1", "RUN install-python.sh"), ]; let result = generate("FROM ubuntu:22.04", &layers, &HashMap::new(), None); insta::assert_snapshot!(result, @r" # Generated by arc-devcontainer FROM ubuntu:22.04 RUN install-node.sh RUN install-python.sh "); } #[test] fn with_env_sorted() { let mut env = HashMap::new(); env.insert("ZEBRA".to_string(), "stripes".to_string()); env.insert("APPLE".to_string(), "red".to_string()); env.insert("MANGO".to_string(), "yellow".to_string()); let result = generate("FROM alpine", &[], &env, None); insta::assert_snapshot!(result, @r" # Generated by arc-devcontainer FROM alpine ENV APPLE=red ENV MANGO=yellow ENV ZEBRA=stripes "); } #[test] fn with_remote_user() { let result = generate("FROM alpine", &[], &HashMap::new(), Some("vscode")); insta::assert_snapshot!(result, @r" # Generated by arc-devcontainer FROM alpine USER vscode "); } #[test] fn all_combined() { let layers = vec![make_layer("node", "node-1", "RUN install-node.sh")]; let mut env = HashMap::new(); env.insert("PATH".to_string(), "/usr/local/bin".to_string()); env.insert("HOME".to_string(), "/home/vscode".to_string()); let result = generate("FROM ubuntu:22.04", &layers, &env, Some("vscode")); insta::assert_snapshot!(result, @r" # Generated by arc-devcontainer FROM ubuntu:22.04 RUN install-node.sh ENV HOME=/home/vscode ENV PATH=/usr/local/bin USER vscode "); } #[test] fn empty_feature_layers_no_extra_blank_lines() { let result = generate("FROM alpine", &[], &HashMap::new(), Some("dev")); insta::assert_snapshot!(result, @r" # Generated by arc-devcontainer FROM alpine USER dev "); } #[test] fn empty_env_map_treated_as_none() { let env = HashMap::new(); let result = generate("FROM alpine", &[], &env, None); insta::assert_snapshot!(result, @r" # Generated by arc-devcontainer FROM alpine "); } #[test] fn multiline_base_dockerfile() { let base = "FROM ubuntu:22.04 AS builder\n\ RUN apt-get update && apt-get install -y build-essential\n\ COPY . /app\n\ RUN make\n\ \n\ FROM ubuntu:22.04\n\ COPY --from=builder /app/bin /usr/local/bin"; let result = generate(base, &[], &HashMap::new(), None); insta::assert_snapshot!(result, @r" # Generated by arc-devcontainer FROM ubuntu:22.04 AS builder RUN apt-get update && apt-get install -y build-essential COPY . /app RUN make FROM ubuntu:22.04 COPY --from=builder /app/bin /usr/local/bin "); } #[test] fn container_env_only() { let mut cenv = HashMap::new(); cenv.insert("DEBIAN_FRONTEND".to_string(), "noninteractive".to_string()); let result = generate("FROM alpine", &[], &cenv, None); insta::assert_snapshot!(result, @r" # Generated by arc-devcontainer FROM alpine ENV DEBIAN_FRONTEND=noninteractive "); } #[test] fn container_env_with_multiple_keys() { let mut cenv = HashMap::new(); cenv.insert("ALPHA".to_string(), "first".to_string()); cenv.insert("BETA".to_string(), "second".to_string()); cenv.insert("GAMMA".to_string(), "third".to_string()); let result = generate("FROM alpine", &[], &cenv, None); insta::assert_snapshot!(result, @r" # Generated by arc-devcontainer FROM alpine ENV ALPHA=first ENV BETA=second ENV GAMMA=third "); } }