diff --git a/conditional-variables/action.yaml b/conditional-variables/action.yaml index a1898ea..d91671c 100644 --- a/conditional-variables/action.yaml +++ b/conditional-variables/action.yaml @@ -1,45 +1,72 @@ + name: Conditional Variables description: | - Evaluates a list of conditions and exposes variables as outputs. - Conditions are evaluated in order. All matching conditions are applied. - If multiple conditions set the same variable, the last matching value wins. + Evaluates a set of rules and exposes variables as outputs. + + Rules are evaluated in the order they are defined. + + A rule matches when any of its conditions match the supplied context. + + If multiple matching rules set the same variable, + the last matching value wins. inputs: context: description: | - Context variables available during condition evaluation. - - conditions: - description: | - List of conditions to evaluate. + Context variables available during rule evaluation. Example: - - when: - branch: - - 9.0-alpine-24 - - 9.0-alpine-23 + branch=9.0-alpine-24 + event=push + install_something=1 + + required: true + + rules: + description: | + Rules to evaluate. + + Example: + + - conditions: + - branch == 9.0-alpine-24 + - branch == 9.0-alpine-23 set: - DOTNET_VERSION: 9.0 + DOTNET_VERSION=9.0 + DOTNET_RUNTIME_VERSION=9.0.18 + + - conditions: + - install_something == 1 + set: + INSTALL_DEPENDENCY=true + + Conditions within a rule are evaluated as OR. required: true fail-on-no-match: description: | - Fail the action when no condition matches. + Fail the action if no rule matches. required: false default: "false" +outputs: + matched: + description: Indicates whether at least one rule matched. + value: ${{ steps.evaluate.outputs.matched }} + runs: using: composite steps: - - name: Evaluate conditions + - name: Evaluate rules + id: evaluate shell: bash env: CONTEXT: ${{ inputs.context }} - CONDITIONS: ${{ inputs.conditions }} + RULES: ${{ inputs.rules }} FAIL_ON_NO_MATCH: ${{ inputs.fail-on-no-match }} run: | - "${{ github.action_path }}/evaluate.sh" \ No newline at end of file + "${{ github.action_path }}/evaluate.sh" diff --git a/conditional-variables/evaluate.sh b/conditional-variables/evaluate.sh index 894eb2d..4bfeda9 100644 --- a/conditional-variables/evaluate.sh +++ b/conditional-variables/evaluate.sh @@ -1,3 +1,8 @@ +declare -A context + +# +# Remove leading/trailing whitespace +# trim() { local var="$*" var="${var#"${var%%[![:space:]]*}"}" @@ -5,48 +10,51 @@ trim() { printf '%s' "$var" } -declare -A context +# +# Load key=value pairs into context array +# +load_context() { + + while IFS='=' read -r key value; do + + key="$(trim "${key:-}")" + value="$(trim "${value:-}")" + + [[ -z "$key" ]] && continue + [[ "$key" =~ ^# ]] && continue + + context["$key"]="$value" + + done <<< "$CONTEXT" +} # -# Load context +# Evaluate a single condition # -while IFS='=' read -r key value; do - - key="$(trim "${key:-}")" - value="$(trim "${value:-}")" - - [[ -z "$key" ]] && continue - [[ "$key" =~ ^# ]] && continue - - context["$key"]="$value" - -done <<< "$CONTEXT" - -matched=false - +# Examples: +# branch == 9.0 +# event != push +# count > 10 # -# Evaluate conditions (OR) -# -while IFS= read -r condition; do +evaluate_condition() { - condition="$(trim "${condition:-}")" + local condition="$1" - [[ -z "$condition" ]] && continue - [[ "$condition" =~ ^# ]] && continue + local variable + local operator + local expected + local actual read -r variable operator expected <<< "$condition" - # - # Validate condition format - # if [[ -z "${variable:-}" || -z "${operator:-}" || -z "${expected:-}" ]]; then echo "Invalid condition: $condition" - exit 1 + return 1 fi if [[ ! -v context["$variable"] ]]; then echo "Unknown context variable '$variable'" - exit 1 + return 1 fi actual="${context[$variable]}" @@ -54,63 +62,78 @@ while IFS= read -r condition; do case "$operator" in "==") - [[ "$actual" == "$expected" ]] && matched=true + [[ "$actual" == "$expected" ]] ;; "!=") - [[ "$actual" != "$expected" ]] && matched=true + [[ "$actual" != "$expected" ]] ;; ">"|">="|"<"|"<=") - # - # Numeric comparisons only - # if [[ ! "$actual" =~ ^-?[0-9]+$ ]]; then echo "Numeric operator '$operator' requires integer value. Actual: '$actual'" - exit 1 + return 1 fi if [[ ! "$expected" =~ ^-?[0-9]+$ ]]; then - echo "Numeric operator '$operator' requires integer comparison value. Expected: '$expected'" - exit 1 + echo "Numeric operator '$operator' requires integer value. Expected: '$expected'" + return 1 fi case "$operator" in ">") - (( actual > expected )) && matched=true + (( actual > expected )) ;; ">=") - (( actual >= expected )) && matched=true + (( actual >= expected )) ;; "<") - (( actual < expected )) && matched=true + (( actual < expected )) ;; "<=") - (( actual <= expected )) && matched=true + (( actual <= expected )) ;; esac ;; *) echo "Unsupported operator: $operator" - exit 1 + return 1 ;; - esac - - # - # OR logic: - # stop at first match - # - [[ "$matched" == true ]] && break - -done <<< "$CONDITIONS" +} # -# Set outputs +# Evaluate list of conditions # -if [[ "$matched" == true ]]; then +# Conditions are OR'd together +# +evaluate_conditions() { + + local matched=false + + while IFS= read -r condition; do + + condition="$(trim "${condition:-}")" + + [[ -z "$condition" ]] && continue + [[ "$condition" =~ ^# ]] && continue + + if evaluate_condition "$condition"; then + matched=true + break + fi + + done <<< "$CONDITIONS" + + [[ "$matched" == true ]] +} + +# +# Write outputs +# +apply_variables() { while IFS= read -r output; do @@ -119,23 +142,36 @@ if [[ "$matched" == true ]]; then [[ -z "$output" ]] && continue [[ "$output" =~ ^# ]] && continue - # - # Validate output syntax - # if [[ ! "$output" =~ ^[A-Za-z_][A-Za-z0-9_]*= ]]; then echo "Invalid variable definition: $output" - exit 1 + return 1 fi echo "$output" >> "$GITHUB_OUTPUT" done <<< "$VARIABLES" +} -fi +# +# Main +# +main() { -if [[ "$matched" == false && "$FAIL_ON_NO_MATCH" == "true" ]]; then - echo "No conditions matched" - exit 1 -fi + load_context -echo "matched=$matched" >> "$GITHUB_OUTPUT" \ No newline at end of file + local matched=false + + if evaluate_conditions; then + matched=true + apply_variables + fi + + if [[ "$matched" == false && "$FAIL_ON_NO_MATCH" == "true" ]]; then + echo "No conditions matched" + exit 1 + fi + + echo "matched=$matched" >> "$GITHUB_OUTPUT" +} + +main \ No newline at end of file