sccmod 0.1.12

A mediocre module manager for handling multiple versions of self-compiled modules
Documentation
use crate::module::{Dependency, Environment, Module};

pub fn generate(module: &Module) -> String {
    // Generate a modulefile with support for flavours
    // The modulefile has the following format:

    /*
        #%Module

        # ${Module Name}
        # ${Module Description}
        # ${Module Metadata}

        # MODULEFILE GENERATED BY SCCMOD
        # https://github.com/Pencilcaseman/sccmod

        package require flavours
        flavours init

        proc ModulesHelp { } {
           puts stderr "
        ${Module Name}: ${Module Description}
        ${Module Metadata}
        "
        }

        module-whatis "${Module Description}"

        # Dependend module classes -- e.g. MPI requires a Compiler
        flavours prereq -class ${Module Dependency: Class}

        # Multiple versions of the same module cannot be loaded simultaneously
        flavours conflict ${Module Name}

        # Root directory of the module, containing all "flavours"
        # e.g.: |-> OpenMPI
        #           |-> gcc-11
        #           |-> gcc-12
        #           |-> gcc-13
        #           |-> clang-17
        #           |-> clang-18
        flavours root     ${Module Root Dir}
        flavours revision 1
        flavours commit

        # Set environment variables
        setenv ${var} ${value}
        # ...

        # Modify environment variables
        # Directories are relative to the "flavour" root
        flavours prepend-path PATH            bin
        flavours prepend-path LIBRARY_PATH    lib
        flavours prepend-path LD_LIBRARY_PATH lib
        flavours prepend-path CPATH           include

        # Cleanup and reload conflicting modules
        flavours cleanup
    */

    let module_name = &module.name;

    let mut module_metadata_str = String::new();
    for (key, value) in module.metadata.iter() {
        module_metadata_str.push_str(&format!("# {key}: {value}\n"));
    }

    let mut module_metadata_str_no_hashes = String::new();
    for (key, value) in module.metadata.iter() {
        module_metadata_str_no_hashes.push_str(&format!("{key}: {value}\n"));
    }

    let no_description_provided = "No description provided".to_string();
    let module_description = module
        .metadata
        .get("description")
        .unwrap_or(&no_description_provided);

    let mut class_definitions = String::new();
    for class in module.dependencies.iter().filter_map(|dep| {
        if let Dependency::Class(name) = dep {
            Some(name)
        } else {
            None
        }
    }) {
        class_definitions.push_str(&format!("flavours prereq -class {class}\n"));
    }

    let root_dir = &module.install_path;

    let mut environment_variables = String::new();
    for (key, value) in module.environment.iter() {
        environment_variables.push_str(&match value {
            Environment::Set(val) => format!("setenv \"{key}\" \"{val}\"\n"),
            Environment::Append(val) => format!("flavours append-path \"{key}\" \"{val}\"\n"),
            Environment::Prepend(val) => format!("flavours prepend-path \"{key}\" \"{val}\"\n"),
        })
    }

    format!(
        r#"#%Module

# MODULEFILE GENERATED BY SCCMOD
# https://github.com/Pencilcaseman/sccmod

# Metadata
{module_metadata_str}

package require flavours
flavours init

proc ModulesHelp {{ }} {{
   puts stderr "
{module_metadata_str_no_hashes}
"
}}

module-whatis "{module_description}"

{class_definitions}

flavours conflict {module_name}

flavours root     {root_dir}
flavours revision 1
flavours commit

{environment_variables}

# Cleanup and reload conflicting modules
flavours cleanup
"#
    )
}