Skip to main content
This guide walks you through adding a new module to bash::framehead. The framework’s modular architecture makes it straightforward to extend with new functionality.

Module structure

Modules in bash::framehead follow a consistent pattern:
  • All functions use the module::function_name naming convention
  • Files are stored in src/ directory as module.sh
  • The runtime module is required by all other modules
  • Modules have minimal coupling to enable flexible composition
The only mandatory module is runtime.sh — all other modules can be mixed and matched based on your needs.

Creating a new module

1

Create the module file

Create a new file in the src/ directory following the naming pattern:
touch src/yourmodule.sh
2

Define your functions

Write functions following the namespace convention:
src/yourmodule.sh
# Convert string to uppercase
# Usage: yourmodule::shout str
yourmodule::shout() {
    string::upper "$1"
}

# Check if value is positive
# Usage: yourmodule::is_positive number
# Example: yourmodule::is_positive 42 && echo "positive"
yourmodule::is_positive() {
    [[ ${1:-0} -gt 0 ]]
}
Function comments directly above definitions are extracted by the wiki generator. Include Usage: and Example: lines for better documentation.
3

Add module to compilation

The compiler automatically discovers .sh files in the src/ directory. No manual registration needed — just ensure your file is in the right place:
src/
├── runtime.sh        # Required
├── array.sh
├── string.sh
└── yourmodule.sh     # ← Your new module
4

Compile the framework

Run the compilation to generate the single-file output:
./main.sh compile
# → compiled.sh
The compiler will:
  • Validate the src/ directory exists
  • Collect all .sh files
  • Run ShellCheck if available
  • Combine into a single executable file
From main.sh:3-26:
compile_files() {
    local output_file="${1:-compiled.sh}"
    local src_dir
    src_dir="$(dirname "${BASH_SOURCE[0]}")/src"

    # Validate src directory exists
    if [[ ! -d "$src_dir" ]]; then
        echo "Error: src directory not found: $src_dir" >&2
        return 1
    fi

    # Collect .sh files upfront
    local -a files=()
    for f in "$src_dir"/*.sh; do
        [[ -f "$f" ]] && files+=("$f")
    done

    # Truncate/create output only after validation
    true > "$output_file"
    # ... compilation continues
}
5

Add tests

Add test coverage in the tester() function in main.sh. Use the helper functions:
# Test exact match
_test "yourmodule::is_positive (true)" "0" "$( yourmodule::is_positive 5; echo $? )"
_test "yourmodule::is_positive (false)" "1" "$( yourmodule::is_positive -5; echo $? )"

# Test contains substring
_test_contains "yourmodule::shout" "HELLO" "$(yourmodule::shout hello)"

# Test non-empty output
_test_nonempty "yourmodule::uuid" "$(yourmodule::uuid)"

# Mark functions as tested
_mark_tested yourmodule::shout yourmodule::is_positive yourmodule::uuid
See the Testing guide for more details on the test framework.
6

Run tests

Verify your module works correctly:
./main.sh test ./compiled.sh
The test output shows pass/fail status with color coding:
--- yourmodule ---
  PASS  yourmodule::is_positive (true)
  PASS  yourmodule::is_positive (false)
  PASS  yourmodule::shout

=== Results: 3 passed, 0 failed, 0 skipped ===
7

Generate documentation

Generate wiki pages from your function comments:
./gen_wiki.sh ./compiled.sh ./wiki
This creates:
  • wiki/yourmodule.md — Module index
  • wiki/yourmodule/*.md — Individual function pages
See Wiki generation for details.

Module best practices

Naming conventions

All functions must follow the module::function pattern:
# ✓ Good
string::upper
math::factorial
fs::exists

# ✗ Bad
str_upper
UpperString
fileExists

Graceful degradation

Check for required external tools and fail cleanly:
mymodule::compress() {
    if ! runtime::has_command gzip; then
        echo "Error: gzip required but not found" >&2
        return 1
    fi
    gzip "$@"
}

Pure Bash where possible

Avoid unnecessary subshells and external commands:
# ✓ Prefer pure Bash
string::upper() {
    echo "${1^^}"
}

# ✗ Avoid when possible
string::upper() {
    echo "$1" | tr '[:lower:]' '[:upper:]'
}
Use external tools only when necessary (e.g., bc for floating-point math). Document the dependency clearly.

No side effects

Functions should not mutate global state:
# ✓ Good — returns output
string::reverse() {
    local str="$1" result=""
    for (( i=${#str}-1; i>=0; i-- )); do
        result+="${str:$i:1}"
    done
    echo "$result"
}

# ✗ Bad — mutates global variable
GLOBAL_RESULT=""
string::reverse() {
    GLOBAL_RESULT="..."  # Side effect!
}

Integration with existing modules

Your module can use functions from other modules:
# Example: Using string:: and fs:: modules
yourmodule::sanitize_filename() {
    local filename="$1"
    filename=$(string::strip_spaces "$filename")
    filename=$(string::replace_all "$filename" "/" "_")
    echo "$filename"
}

yourmodule::safe_write() {
    local file="$1" content="$2"
    if fs::is_writable "$(fs::path::dirname "$file")"; then
        fs::write "$file" "$content"
    else
        echo "Error: Cannot write to $file" >&2
        return 1
    fi
}

Example: Complete minimal module

Here’s a complete example of a simple utility module:
src/util.sh
#!/usr/bin/env bash

# Generate a random ID
# Usage: util::random_id [length]
# Example: util::random_id 8
util::random_id() {
    local len="${1:-16}"
    string::random "$len"
}

# Check if running in CI environment
# Usage: util::is_ci
util::is_ci() {
    runtime::is_ci
}

# Pretty print a hash map
# Usage: util::print_map key1 value1 key2 value2
util::print_map() {
    while (( $# >= 2 )); do
        printf "%-20s %s\n" "$1:" "$2"
        shift 2
    done
}

# Retry a command with exponential backoff
# Usage: util::retry_exponential max_attempts command [args...]
util::retry_exponential() {
    local max="$1"; shift
    local attempt=1
    while (( attempt <= max )); do
        "$@" && return 0
        local delay=$((2 ** attempt))
        echo "Attempt $attempt failed, retrying in ${delay}s..." >&2
        sleep "$delay"
        (( attempt++ ))
    done
    return 1
}

Next steps

Testing

Learn how to write comprehensive tests for your module

Wiki generation

Generate documentation from your code comments

Contributing

Submit your module to the main repository

Build docs developers (and LLMs) love