Created
February 8, 2018 17:59
-
-
Save jlinoff/d7e7d789b32c9caeb1ada7877e1f1a3f to your computer and use it in GitHub Desktop.
Use bash introspection to enable simple state handling.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# | |
# Tests the state machine handler. | |
# Define 5 functions, auto-generate a state machine control mechanism to | |
# determine which functions are executed. | |
# | |
# License: MIT Open Source | |
# Copyright (c) 2018 by Joe Linoff | |
source state-machine.sh | |
# ================================================================ | |
# Functions | |
# ================================================================ | |
function s01:func() { | |
if ! _state_enabled ; then return 0 ; fi | |
echo "Function: ${FUNCNAME[0]}" | |
} | |
function s02:func() { | |
if ! _state_enabled ; then return 0 ; fi | |
echo "Function: ${FUNCNAME[0]}" | |
} | |
function s03:func() { | |
if ! _state_enabled ; then return 0 ; fi | |
echo "Function: ${FUNCNAME[0]}" | |
} | |
function s04:func() { | |
if ! _state_enabled ; then return 0 ; fi | |
echo "Function: ${FUNCNAME[0]}" | |
} | |
function s05:func() { | |
if ! _state_enabled ; then return 0 ; fi | |
echo "Function: ${FUNCNAME[0]}" | |
} | |
# ================================================================ | |
# Main | |
# ================================================================ | |
_state_init | |
# Figure out what to print. | |
if ! _state_define_range $1 ; then | |
echo "ERROR: Invalid range: '$1'." | |
exit 1 | |
fi | |
s01:func | |
s02:func | |
s03:func | |
s04:func | |
s05:func |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# | |
# Implements a state machine for functions of the form: | |
# s##:<func-name> | |
# | |
# Examples: | |
# s01:_clean | |
# s02:_create_build_directory | |
# | |
# It is used like this. | |
# 1. call _state_init early in the program. It parses | |
# the bash script using introspection and uses sets | |
# up the state infrastructure. | |
# | |
# 2. In each function with the S##: prefix, put this | |
# at the top: | |
# if ! _state_enabled ; then return 0 ; fi | |
# | |
# 3. Add this to the argument parsing to process the | |
# range information: | |
# if ! _state_define_range $1 ; then | |
# echo "ERROR: Invalid range: '$1'." | |
# exit 1 | |
# fi | |
# Valid ranges are described in the comments for | |
# for the _state_define_range function. | |
# | |
# License: MIT Open Source | |
# Copyright (c) 2018 by Joe Linoff | |
# ================================================================ | |
# Functions | |
# ================================================================ | |
# Get the state id from the function name. | |
# We expect a s##: prefix. | |
function _state_get_id() { | |
local FuncName="$1" | |
echo $FuncName | awk -F: '{x=substr($1,2); gsub("^0*", "", x); print x}' | |
} | |
# Determine whether the caller should execute. | |
# Here is how the function should test: | |
# if ! _state_enabled ${FUNCNAME[0]} ; then return 0 ; fi | |
# OR | |
# if ! _state_enabled ; then return 0 ; fi | |
function _state_enabled() { | |
local FuncName="$1" | |
[ -z "$FuncName" ] && FuncName=${FUNCNAME[1]} | |
local StateId=$(_state_get_id $FuncName) | |
return ${_STATE_TABLE[$StateId]} | |
} | |
# Initialize the state handling. | |
# When this function completes, all state functions will have been | |
# analyzed using bash introspection and a global array of states | |
# called _STATE_TABLE will have been declared and populated. | |
# The contents of this array can be used by each state function to | |
# determine whether it should execute. | |
# It also defines three constants: | |
# _STATE_ENABLE - enables a specific state | |
# _STATE_DISABLE - disables a specific state | |
# _STATE_TABLE_MAX - max entry in the state table (for range checking) | |
function _state_init() { | |
local Verbose=0 | |
[ -n "$1" ] && Verbose=1 | |
# Find the state functions. | |
(( Verbose )) && echo "Function: ${FUNCNAME[0]}" || true | |
local Names=($(set | grep '^s[0-9][0-9]:' | awk '{print $1}') ) | |
# Iterate over them to find the maximum. | |
_STATE_TABLE_MAX=0 | |
for Name in ${Names[@]} ; do | |
local Id=$(_state_get_id $Name) | |
(( Verbose )) && echo " State: $Id $Name" || true | |
(( Id > _STATE_TABLE_MAX )) && _STATE_TABLE_MAX=$Id || true | |
done | |
(( Verbose )) && echo " Max: $_STATE_TABLE_MAX" || true | |
# Declare a global array based on the maximum. | |
declare -a _STATE_TABLE | |
for (( i=0; i<=_STATE_TABLE_MAX; i++ )) ; do | |
_STATE_TABLE[$i]=0 | |
done | |
_STATE_ENABLE=0 | |
_STATE_DISABLE=1 | |
} | |
# Parse the state range specification. | |
# The state range specification defines a subset of the available | |
# states that will be executed. | |
# | |
# The form of the range is <BEG>:<END> where <BEG> is the first state | |
# function in the range to execute and <END> is the last (inclusive). | |
# If <BEG> is not specified, the first state function is assumed. | |
# If <ENV> is not specified, the last state function is assumed. | |
# | |
# Examples: | |
# 3 Only execute state 3, all others are disabled. | |
# :3 Execute all states less than or equal to state 3. | |
# 0:3 Execute all states less than or equal to state 3. | |
# 3: Execute all states greater than or equal to state 3. | |
# 3:5 Execute all states 3, 4 and 5. | |
# When this function is complete the global state table will be set | |
# properly for enabling and disabling state functions. | |
function _state_define_range() { | |
local Range="$1" | |
local RangeBeg=0 | |
local RangeEnd=0 | |
if [ -n "$Range" ] ; then | |
# Parse the range. | |
if [[ "$Range" =~ ^[0-9]+$ ]] ; then | |
RangeBeg=$Range | |
RangeEnd=$Range | |
elif [[ "$Range" =~ ^[0-9]+:[0-9]+$ ]] ; then | |
RangeBeg=$(echo "$Range" | awk -F: '{print $1}') | |
RangeEnd=$(echo "$Range" | awk -F: '{print $2}') | |
elif [[ "$Range" =~ ^:[0-9]+$ ]] ; then | |
RangeBeg=1 | |
RangeEnd=$(echo "$Range" | awk -F: '{print $1}') | |
elif [[ "$Range" =~ ^[0-9]+:$ ]] ; then | |
RangeBeg=$(echo "$Range" | awk -F: '{print $1}') | |
RangeEnd=$_STATE_TABLE_MAX | |
else | |
# Error, unrecognized range. | |
return 1 | |
fi | |
# Is there anything to do? | |
if (( RangeEnd < RangeBeg )) ; then | |
# The convention is have a monotonically increasing range. | |
return 0 | |
fi | |
if (( RangeBeg > _STATE_TABLE_MAX )) ; then | |
# The beginning is out of range, there is nothing to do. | |
return 0 ; | |
fi | |
if (( RangeEnd < 1 )) ; then | |
# The end is out of range, there is nothing to do. | |
return 0 ; | |
fi | |
# Set the states. | |
for (( i=1 ; i <= _STATE_TABLE_MAX ; i++ )) ; do | |
if (( i < RangeBeg )) || (( i > RangeEnd )) ; then | |
_STATE_TABLE[$i]=$_STATE_DISABLE | |
else | |
_STATE_TABLE[$i]=$_STATE_ENABLE | |
fi | |
done | |
fi | |
return 0 | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment