k8src 0.20.0

k8src is the kubernetes rc scripting language
Documentation
use utf8path::Path;

const VARIABLES: &[(&str, &str)] = &[
    ("SALUTATION", "Hello Alice!"),
    ("foo1_ENABLED", "YES"),
    ("foo1_ENABLED", "NO"),
    ("foo1_SALUTATION", "Hej Bob!"),
    ("bar7_SALUTATION", "Привет Charlie"),
];

const FILES: &[(&str, &str)] = &[
    (
        "service.yaml.template",
        "source: this is the generic template
salutation: ${SALUTATION:-<UNSET>}",
    ),
    (
        "rc.d/foo1.yaml.template",
        "source: this is foo1 template
salutation: ${SALUTATION:-<UNSET>}",
    ),
    (
        "rc.d/bar7.yaml.template",
        "source: this is bar7 template
salutation: ${SALUTATION:-<UNSET>}",
    ),
];

fn n_choose_k(k: usize, n: usize) -> impl Iterator<Item = usize> {
    assert!(n < 64);
    assert!(k <= n);
    struct Count {
        k: usize,
        n: usize,
        i: usize,
    }
    impl Iterator for Count {
        type Item = usize;

        fn next(&mut self) -> Option<Self::Item> {
            while self.i < 1 << self.n {
                let i = self.i;
                self.i += 1;
                if i.count_ones() as usize <= self.k {
                    return Some(i);
                }
            }
            None
        }
    }
    Count { k, n, i: 0 }
}

fn generate_combinations<'a, 'b>(
    inputs: &'a [(&'b str, &'b str)],
    k: usize,
) -> impl Iterator<Item = Vec<(&'b str, &'b str)>> + 'a {
    assert!(k <= inputs.len());
    struct Combinate<'b, 'c, I: Iterator<Item = usize>> {
        combinations: I,
        inputs: &'b [(&'c str, &'c str)],
    }
    impl<'c, I: Iterator<Item = usize>> Iterator for Combinate<'_, 'c, I> {
        type Item = Vec<(&'c str, &'c str)>;

        fn next(&mut self) -> Option<Self::Item> {
            'combinating: for mask in self.combinations.by_ref() {
                for i in 0..self.inputs.len() {
                    for j in i + 1..self.inputs.len() {
                        if mask & (1 << i) != 0
                            && mask & (1 << j) != 0
                            && self.inputs[i].0 == self.inputs[j].0
                        {
                            continue 'combinating;
                        }
                    }
                }
                let mut result = Vec::with_capacity(mask.count_ones() as usize);
                for i in 0..self.inputs.len() {
                    if mask & (1 << i) != 0 {
                        result.push(self.inputs[i]);
                    }
                }
                return Some(result);
            }
            None
        }
    }
    Combinate {
        combinations: n_choose_k(k, inputs.len()),
        inputs,
    }
}

fn main() {
    let mut enabled_rc_confs = vec![];
    for vars in generate_combinations(VARIABLES, 3) {
        let mut rc_conf = r#"NAMESPACE="symphhonize"
foo1_IMAGE="foo1:latest"
foo1_PORT="6600"
"#
        .to_string();
        let mut enabled = false;
        for (key, value) in vars {
            if key == "foo1_ENABLED" && value == "YES" {
                enabled = true;
            }
            rc_conf += &format!("{key}={value:?}\n")
        }
        if enabled {
            enabled_rc_confs.push(rc_conf);
        }
    }
    let mut files = FILES.to_vec();
    files.extend(enabled_rc_confs.iter().map(|x| ("rc.conf", x.as_str())));
    let mut idx = 0;
    for files in generate_combinations(&files, 4) {
        let case_path = Path::from(format!("tests/cases/{idx}"));
        if case_path
            .exists()
            .expect("should be able to inspect case path")
        {
            std::fs::remove_dir_all(&case_path).expect("should be able to remove directory");
        }
        if !files.iter().any(|f| f.0 == "rc.conf") {
            continue;
        }
        for (file, contents) in files {
            let file = case_path.join(file);
            std::fs::create_dir_all(file.dirname()).expect("create dir should succeed");
            std::fs::write(file, contents).expect("writing file should succeed");
        }
        idx += 1;
    }
}