Files
goose/documentation/automation/recipe-schema-tracking/scripts/extract-validation-structure.sh
T

186 lines
5.4 KiB
Bash
Executable File

#!/bin/bash
# Extract validation structure from Rust source files
# Usage: ./extract-validation-structure.sh <version>
# Example: ./extract-validation-structure.sh v1.15.0
set -e
VERSION=${1:-"main"}
GOOSE_REPO=${GOOSE_REPO:-"$HOME/Development/goose"}
if [ ! -d "$GOOSE_REPO" ]; then
echo "Error: GOOSE_REPO directory not found: $GOOSE_REPO" >&2
exit 1
fi
cd "$GOOSE_REPO"
# Verify version exists (for non-main versions)
if [ "$VERSION" != "main" ]; then
if ! git rev-parse "$VERSION" >/dev/null 2>&1; then
echo "Error: Version $VERSION not found in git history" >&2
exit 1
fi
fi
# Start JSON output
# Use ISO 8601 format that works on both macOS and Linux
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || date -u -Iseconds 2>/dev/null || date -u)
cat << EOF
{
"version": "$VERSION",
"extracted_at": "$TIMESTAMP",
"struct_fields": [
EOF
# Extract fields from multiple structs
FIRST_FIELD=true
# Get file content from git history or working directory
if [ "$VERSION" = "main" ]; then
MOD_RS_CONTENT=$(cat crates/goose/src/recipe/mod.rs)
else
MOD_RS_CONTENT=$(git show "$VERSION:crates/goose/src/recipe/mod.rs" 2>/dev/null || {
echo "Error: Failed to read mod.rs from version $VERSION" >&2
exit 1
})
fi
# List of structs to extract (in order of appearance in file)
STRUCTS="Recipe Author Settings Response SubRecipe RecipeParameter"
# Create a temporary file to collect all fields
TEMP_FIELDS=$(mktemp)
for STRUCT_NAME in $STRUCTS; do
# Extract just this struct's definition (from "pub struct Name" to the closing "}")
echo "$MOD_RS_CONTENT" | awk "
/^pub struct $STRUCT_NAME/ { in_struct=1; next }
in_struct && /^}/ { exit }
in_struct && /^[[:space:]]+pub [a-z_]+:/ { print }
" | while IFS= read -r line; do
# Extract field name (word after 'pub ')
field_name=$(echo "$line" | sed -E 's/.*pub ([a-z_]+):.*/\1/')
# Extract type (between : and , or // or end of line)
field_type=$(echo "$line" | sed -E 's/.*:\s*([^,\/]+).*/\1/' | sed 's/[[:space:]]*$//')
# Extract inline comment (after //)
inline_comment=""
if echo "$line" | grep -q "//"; then
inline_comment=$(echo "$line" | sed -E 's/.*\/\/\s*(.*)$/\1/' | sed 's/[[:space:]]*$//' | sed 's/"/\\"/g')
fi
# Check if optional
is_optional="false"
if echo "$field_type" | grep -q "Option<"; then
is_optional="true"
fi
# Output to temp file
cat << FIELD_JSON >> "$TEMP_FIELDS"
{
"struct": "$STRUCT_NAME",
"field": "$field_name",
"type": "$field_type",
"optional": $is_optional,
"inline_comment": "$inline_comment"
}
FIELD_JSON
done
done
# Output fields with proper comma separation
if [ -s "$TEMP_FIELDS" ]; then
# Read all JSON objects into an array and format with commas
jq -s '.' "$TEMP_FIELDS" | jq -r 'to_entries | .[] | (if .key > 0 then "," else "" end) + " " + (.value | tostring)'
fi
rm -f "$TEMP_FIELDS"
# Close struct_fields array, start validation_functions
cat << EOF
],
"validation_functions": [
EOF
# Extract validation functions with error messages and code snippets
FIRST_FUNC=true
# Get validation file content from git history or working directory
# Note: validate_recipe.rs may not exist in older versions
if [ "$VERSION" = "main" ]; then
if [ -f crates/goose/src/recipe/validate_recipe.rs ]; then
VALIDATE_RS_CONTENT=$(cat crates/goose/src/recipe/validate_recipe.rs)
else
VALIDATE_RS_CONTENT=""
fi
else
VALIDATE_RS_CONTENT=$(git show "$VERSION:crates/goose/src/recipe/validate_recipe.rs" 2>/dev/null || echo "")
fi
if [ -n "$VALIDATE_RS_CONTENT" ]; then
echo "$VALIDATE_RS_CONTENT" | rg "^fn validate_" -A 30 | \
awk '
/^fn validate_/ {
if (func_name != "") {
# Output previous function
if (first_func == "true") {
first_func = "false"
} else {
print ","
}
printf " {\n"
printf " \"function\": \"%s\",\n", func_name
printf " \"signature\": \"%s\",\n", signature
printf " \"error_messages\": [%s],\n", error_msgs
printf " \"code_snippet\": %s\n", code_snippet
printf " }"
}
# Start new function
func_name = $0
gsub(/^fn /, "", func_name)
gsub(/\(.*/, "", func_name)
signature = $0
gsub(/"/, "\\\"", signature)
error_msgs = ""
code_snippet = "\"...\""
first_func = (first_func == "") ? "true" : first_func
}
/anyhow::anyhow!\("/ {
# Extract error message
msg = $0
gsub(/.*anyhow::anyhow!\("/, "", msg)
gsub(/".*/, "", msg)
gsub(/"/, "\\\"", msg)
if (error_msgs != "") error_msgs = error_msgs ", "
error_msgs = error_msgs "\"" msg "\""
}
END {
# Output last function
if (func_name != "") {
if (first_func != "true") {
print ","
}
printf " {\n"
printf " \"function\": \"%s\",\n", func_name
printf " \"signature\": \"%s\",\n", signature
printf " \"error_messages\": [%s],\n", error_msgs
printf " \"code_snippet\": %s\n", code_snippet
printf " }"
}
}
'
fi
# Close validation_functions array and JSON
cat << EOF
]
}
EOF