Scott M. Mcdermott

UNIX Systems & Network Administrator
available for contract or salaried positions

include

#
# Source file inclusion subsystem (i.e. "shell library")
#
# The inclusion subsystem allows for selective import of the
# shell environment from small source file components, with
# easy source file separation.
#
# Two new functions comprise the include subsystem:
#
#   'include':  source all files in an include directory
#   'require':  source a single file from an include directory
#
# To convert existing kitchen-sink scripts:
#
#  1. break out into logically separate source files
#  2. place related source files into named subdirectories
#  3. use 'include' and 'require' within source files as needed
#  4. in main executable script, source this file at the top
#  5. then bring in source files with 'require' or 'include'
#
# Source files and their contents are available selectively
# to each script, maintained separately and only included as
# needed on a per-script basis.
#
# directories:
#
#   Source files are placed in subdirectories of the parent
#   directory containing this file.  Each of those
#   directories corresponds to an 'include'.
#
# files:
#
#   Individual source files are searched for by name in any
#   of the directories, but only one match is allowed, so
#   they must be unique.
#
# Each sourced file should list its dependencies on other
# source files at the top, and duplicate sourcing is
# prevented.  Interdependencies are brought in automatically
# in the sourced files from their own use of 'require'
# without the user having to know those details in the main
# calling script, or know anything about the necessary
# sourcing order.
#
# Ideally, each 'require' (i.e. source file) contains only a
# single function, and each 'include' pulls in a set of
# related functions, all grouped in one directory.  However,
# any ratio of functions to source files which fits the
# situation (or form a logical unit) can be used.
#
##############################################################################

# so we can be used in different places without hardcoding a
# base directory, we base everything relative to the
# location of this file as first included from the toplevel
# script
#
LIBSH_BASEDIR=${LIBSH_BASEDIR:-${BASH_SOURCE%*/*}}

# we use some shell features which are probably not
# available on some older systems
#
((BASH_VERSINFO[0] < 2)) && return 1

# use this function to include individual source files by
# name, in any subdirectory of the one this script is in
#
require ()
{
    local +i sourcefile
    local -a matched

    sourcefile=$1

    (($# < 1)) && return 5
    [[ $sourcefile =~ [^[:alnum:]_] ]] && return 10

    # don't source it more than once, so sourced files
    # can themselves 'require' and 'include' each other;
    # unfortunately we have to pollute the environment
    # for this, as scanning arrays with strcmp() would be
    # too slow; we need a hash, and we aren't using bash4
    # (yet!), so use the only hash available: the
    # environment
    #
    if compgen -A variable LIBSH_SOURCES_$sourcefile &>/dev/null
    then return
    else
        # XXX BASH WEIRDISM SPOOKY STUFF DESTROY USELESS BAGGAGE:
        # 1: using "declare" within functions makes it local !
        # 2: without "eval" this breaks! even using ': var=""' !
        eval LIBSH_SOURCES_$sourcefile= # just 'declare' breaks!
    fi

    # rely on shell globbing to find the source file for
    # us, but don't require the user to have any
    # particular glob settings (yes, this is needed)
    #
    matched=($(compgen -G "$LIBSH_BASEDIR/*/$sourcefile"))

    # we actually have a flat namespace for the moment,
    # despite the fact that we can group into separate
    # directories; goal is for 'include' and 'require' to be
    # as lightweight as possible
    #
    ((${#matched[@]} == 1)) || return 20

    # at this point, all our sanity checks have concluded
    # successfully, so we go ahead and read the requested
    # sourcefile
    #
    source $matched || return 25

    # recurse for any remaining arguments, to support
    # mutli-arg "require" lines, etc
    #
    shift

    if (($#))
    then require $@ || return $?
    else return 0
    fi
}

# source all files in a subdirectory of the one this script
# is in when sourced
#
include ()
{
    local -i i
    local +i sourcedir
    local +i match
    local -a matched

    sourcedir=$LIBSH_BASEDIR/$1
    (($# < 1)) && return 55
    [[ $sourcedir =~ '[^[:alnum:]_/-\.]' ]] && return 60
    test -d $sourcedir || return 65

    matched=($(compgen -G "$sourcedir/*"))
    for ((i = 0; i < ${#matched[@]}; i++))
    do
        match=${matched[i]}
        require ${match##*/} || return $?
    done
}
# vim:syn=sh:ft=sh