generated from DevPlus1/Unity-Customer-Template
Initial commit
This commit is contained in:
commit
ed87277ef8
64 changed files with 4639 additions and 0 deletions
675
tools/generate-code-docs.sh
Normal file
675
tools/generate-code-docs.sh
Normal file
|
|
@ -0,0 +1,675 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -u -o pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
REPORT_DIR="$ROOT_DIR/reports"
|
||||
API_REPORT="$REPORT_DIR/api-reference.md"
|
||||
FILE_PURPOSE_REPORT="$REPORT_DIR/file-purpose.md"
|
||||
UNITY_REPORT="$REPORT_DIR/unity-scripts.md"
|
||||
CALL_MAP_REPORT="$REPORT_DIR/call-map.md"
|
||||
SUMMARY_REPORT="$REPORT_DIR/code-docs-summary.md"
|
||||
GENERATED_REPORT="$REPORT_DIR/generated-files.md"
|
||||
|
||||
SOURCE_FILES=()
|
||||
DETECTED_CODE_TYPES=()
|
||||
GENERATED_FILES=()
|
||||
|
||||
SOURCE_COUNT=0
|
||||
FUNCTION_COUNT=0
|
||||
CLASS_COUNT=0
|
||||
UNITY_SCRIPT_COUNT=0
|
||||
ENTRY_POINT_COUNT=0
|
||||
|
||||
EXCLUDES=(
|
||||
".git"
|
||||
".forgejo"
|
||||
"node_modules"
|
||||
"vendor"
|
||||
"Library"
|
||||
"Temp"
|
||||
"Logs"
|
||||
"obj"
|
||||
"bin"
|
||||
".vscode"
|
||||
".cache"
|
||||
)
|
||||
|
||||
mkdir -p "$REPORT_DIR" || {
|
||||
printf 'Failed to create reports directory: %s\n' "$REPORT_DIR" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
relative_path() {
|
||||
local path="$1"
|
||||
path="${path#"$ROOT_DIR"/}"
|
||||
printf '%s' "$path"
|
||||
}
|
||||
|
||||
record_generated_file() {
|
||||
local file="$1"
|
||||
local rel="${file#"$ROOT_DIR"/}"
|
||||
local existing
|
||||
|
||||
for existing in "${GENERATED_FILES[@]}"; do
|
||||
[[ "$existing" == "$rel" ]] && return
|
||||
done
|
||||
|
||||
GENERATED_FILES+=("$rel")
|
||||
}
|
||||
|
||||
append_detected_type() {
|
||||
local type="$1"
|
||||
local existing
|
||||
|
||||
for existing in "${DETECTED_CODE_TYPES[@]}"; do
|
||||
[[ "$existing" == "$type" ]] && return
|
||||
done
|
||||
|
||||
DETECTED_CODE_TYPES+=("$type")
|
||||
}
|
||||
|
||||
collect_source_files() {
|
||||
local find_args=("$ROOT_DIR")
|
||||
local exclude
|
||||
|
||||
find_args+=("(")
|
||||
for exclude in "${EXCLUDES[@]}"; do
|
||||
find_args+=(-name "$exclude" -o)
|
||||
done
|
||||
unset 'find_args[${#find_args[@]}-1]'
|
||||
find_args+=(")" -prune -o)
|
||||
find_args+=("(")
|
||||
find_args+=(-name '*.php' -o -name '*.py' -o -name '*.js' -o -name '*.ts' -o -name '*.cs' -o -name '*.c' -o -name '*.cpp' -o -name '*.h' -o -name '*.hpp' -o -name '*.rb' -o -name '*.pl' -o -name '*.pm' -o -name '*.sh' -o -name '*.html' -o -name '*.htm' -o -name '*.css')
|
||||
find_args+=(")" -type f -print0)
|
||||
|
||||
while IFS= read -r -d '' file; do
|
||||
SOURCE_FILES+=("$file")
|
||||
done < <(find "${find_args[@]}" | sort -z)
|
||||
}
|
||||
|
||||
language_for_file() {
|
||||
local file="$1"
|
||||
case "$file" in
|
||||
*.php) printf 'PHP' ;;
|
||||
*.py) printf 'Python' ;;
|
||||
*.js) printf 'JavaScript' ;;
|
||||
*.ts) printf 'TypeScript' ;;
|
||||
*.cs) printf 'C#' ;;
|
||||
*.c|*.cpp|*.h|*.hpp) printf 'C/C++' ;;
|
||||
*.rb) printf 'Ruby' ;;
|
||||
*.pl|*.pm) printf 'Perl' ;;
|
||||
*.sh) printf 'Shell' ;;
|
||||
*.html|*.htm) printf 'HTML' ;;
|
||||
*.css) printf 'CSS' ;;
|
||||
*) printf 'Unknown' ;;
|
||||
esac
|
||||
}
|
||||
|
||||
line_count() {
|
||||
wc -l <"$1" | tr -d ' '
|
||||
}
|
||||
|
||||
join_names() {
|
||||
awk 'NF {gsub(/^[[:space:]]+|[[:space:]]+$/, ""); if (!seen[$0]++) { if (count++) printf ", "; printf "%s", $0 }}'
|
||||
}
|
||||
|
||||
guess_purpose() {
|
||||
local file="$1"
|
||||
local rel base lower language
|
||||
|
||||
rel="$(relative_path "$file")"
|
||||
base="$(basename "$file")"
|
||||
lower="$(printf '%s' "$rel" | tr '[:upper:]' '[:lower:]')"
|
||||
language="$(language_for_file "$file")"
|
||||
|
||||
case "$base" in
|
||||
index.php|index.html|index.htm|main.py|app.py|server.py|server.js|app.js|index.js|Program.cs|Main.cs|main.cpp|main.c)
|
||||
printf 'Likely entry point'
|
||||
return
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$lower" in
|
||||
*config*|*settings*|*.env*) printf 'Possible configuration file' ;;
|
||||
*controller*) printf 'Likely controller or request handler' ;;
|
||||
*model*) printf 'Likely model or data object' ;;
|
||||
*view*|*template*) printf 'Likely view/template' ;;
|
||||
*test*|*sample*) printf 'Likely test/sample file' ;;
|
||||
*database*|*db*) printf 'Possible database access or database configuration' ;;
|
||||
*api*) printf 'Possible API endpoint or API helper' ;;
|
||||
*util*|*helper*) printf 'Likely utility/helper' ;;
|
||||
*manager*) printf 'Likely manager/controller script' ;;
|
||||
*player*) printf 'Possible player controller or player state script' ;;
|
||||
*enemy*) printf 'Possible enemy AI or enemy state script' ;;
|
||||
*health*|*damage*) printf 'Possible health/damage system' ;;
|
||||
*)
|
||||
if [[ "$language" == "HTML" ]]; then
|
||||
printf 'Likely page/template'
|
||||
elif [[ "$language" == "CSS" ]]; then
|
||||
printf 'Likely stylesheet'
|
||||
elif [[ "$language" == "Shell" ]]; then
|
||||
printf 'Possible automation or launch script'
|
||||
else
|
||||
printf 'Possible source module'
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
is_entry_point() {
|
||||
local base
|
||||
base="$(basename "$1")"
|
||||
case "$base" in
|
||||
index.php|index.html|index.htm|main.py|app.py|server.py|server.js|app.js|index.js|Program.cs|Main.cs|main.cpp|main.c)
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
|
||||
if [[ "$1" == *.cs ]] && grep -Eq 'static[[:space:]]+.*Main[[:space:]]*\(' "$1"; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
extract_classes() {
|
||||
local file="$1"
|
||||
local language
|
||||
language="$(language_for_file "$file")"
|
||||
|
||||
case "$language" in
|
||||
PHP)
|
||||
grep -Eoh '^[[:space:]]*(abstract[[:space:]]+|final[[:space:]]+)?class[[:space:]]+[A-Za-z_][A-Za-z0-9_]*|^[[:space:]]*interface[[:space:]]+[A-Za-z_][A-Za-z0-9_]*|^[[:space:]]*trait[[:space:]]+[A-Za-z_][A-Za-z0-9_]*' "$file" | awk '{print $NF}'
|
||||
;;
|
||||
Python)
|
||||
grep -Eoh '^[[:space:]]*class[[:space:]]+[A-Za-z_][A-Za-z0-9_]*' "$file" | awk '{print $2}'
|
||||
;;
|
||||
JavaScript|TypeScript)
|
||||
grep -Eoh '^[[:space:]]*(export[[:space:]]+default[[:space:]]+|export[[:space:]]+)?class[[:space:]]+[A-Za-z_$][A-Za-z0-9_$]*' "$file" | awk '{print $NF}'
|
||||
;;
|
||||
C#)
|
||||
grep -Eoh '^[[:space:]]*(public|private|protected|internal)?[[:space:]]*(abstract|sealed|static|partial)?[[:space:]]*(class|struct|interface)[[:space:]]+[A-Za-z_][A-Za-z0-9_]*' "$file" | awk '{print $NF}'
|
||||
;;
|
||||
C/C++)
|
||||
grep -Eoh '^[[:space:]]*(class|struct)[[:space:]]+[A-Za-z_][A-Za-z0-9_]*' "$file" | awk '{print $2}'
|
||||
;;
|
||||
Ruby)
|
||||
grep -Eoh '^[[:space:]]*(class|module)[[:space:]]+[A-Za-z_:][A-Za-z0-9_:]*' "$file" | awk '{print $2}'
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
extract_functions() {
|
||||
local file="$1"
|
||||
local language
|
||||
language="$(language_for_file "$file")"
|
||||
|
||||
case "$language" in
|
||||
PHP)
|
||||
grep -En '^[[:space:]]*((public|protected|private|static|final|abstract)[[:space:]]+)*function[[:space:]]+[A-Za-z_][A-Za-z0-9_]*[[:space:]]*\(' "$file" || true
|
||||
;;
|
||||
Python)
|
||||
grep -En '^[[:space:]]*(async[[:space:]]+)?def[[:space:]]+[A-Za-z_][A-Za-z0-9_]*[[:space:]]*\(' "$file" || true
|
||||
;;
|
||||
JavaScript|TypeScript)
|
||||
grep -En '^[[:space:]]*(export[[:space:]]+)?(async[[:space:]]+)?function[[:space:]]+[A-Za-z_$][A-Za-z0-9_$]*[[:space:]]*\(|^[[:space:]]*(export[[:space:]]+)?(const|let|var)[[:space:]]+[A-Za-z_$][A-Za-z0-9_$]*[[:space:]]*=[[:space:]]*(async[[:space:]]*)?(\([^)]*\)|[A-Za-z_$][A-Za-z0-9_$]*)[[:space:]]*=>|^[[:space:]]*(public|private|protected)?[[:space:]]*(async[[:space:]]+)?[A-Za-z_$][A-Za-z0-9_$]*[[:space:]]*\([^)]*\)[[:space:]]*\{' "$file" || true
|
||||
;;
|
||||
C#)
|
||||
grep -En '^[[:space:]]*(public|private|protected|internal)?[[:space:]]*(static[[:space:]]+)?(async[[:space:]]+)?[A-Za-z_][A-Za-z0-9_<>,\[\]\?]*[[:space:]]+[A-Za-z_][A-Za-z0-9_]*[[:space:]]*\([^;]*\)[[:space:]]*(\{|=>)' "$file" || true
|
||||
;;
|
||||
C/C++)
|
||||
grep -En '^[[:space:]]*[A-Za-z_][A-Za-z0-9_:<>\*&[:space:]]+[A-Za-z_][A-Za-z0-9_:]*[[:space:]]*\([^;]*\)[[:space:]]*(\{|$)' "$file" || true
|
||||
;;
|
||||
Ruby)
|
||||
grep -En '^[[:space:]]*def[[:space:]]+[A-Za-z_][A-Za-z0-9_!?=]*' "$file" || true
|
||||
;;
|
||||
Perl)
|
||||
grep -En '^[[:space:]]*sub[[:space:]]+[A-Za-z_][A-Za-z0-9_]*' "$file" || true
|
||||
;;
|
||||
Shell)
|
||||
grep -En '^[[:space:]]*([A-Za-z_][A-Za-z0-9_]*[[:space:]]*\(\)|function[[:space:]]+[A-Za-z_][A-Za-z0-9_]*)' "$file" || true
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
extract_imports() {
|
||||
local file="$1"
|
||||
local language
|
||||
language="$(language_for_file "$file")"
|
||||
|
||||
case "$language" in
|
||||
PHP)
|
||||
grep -Eoh '^[[:space:]]*(include|include_once|require|require_once)[[:space:]]*\(?[^;]+|^[[:space:]]*use[[:space:]]+[^;]+' "$file" || true
|
||||
;;
|
||||
Python)
|
||||
grep -Eoh '^[[:space:]]*(import|from)[[:space:]].+' "$file" || true
|
||||
;;
|
||||
JavaScript|TypeScript)
|
||||
grep -Eoh '^[[:space:]]*import[[:space:]].+|require[[:space:]]*\([^)]+\)' "$file" || true
|
||||
;;
|
||||
C#)
|
||||
grep -Eoh '^[[:space:]]*using[[:space:]][^;]+' "$file" || true
|
||||
;;
|
||||
C/C++)
|
||||
grep -Eoh '^[[:space:]]*#include[[:space:]]+[<"][^>"]+[>"]' "$file" || true
|
||||
;;
|
||||
Ruby)
|
||||
grep -Eoh '^[[:space:]]*require(_relative)?[[:space:]].+' "$file" || true
|
||||
;;
|
||||
Perl)
|
||||
grep -Eoh '^[[:space:]]*(use|require)[[:space:]].+' "$file" || true
|
||||
;;
|
||||
Shell)
|
||||
grep -Eoh '^[[:space:]]*(source|\.)[[:space:]].+' "$file" || true
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
function_name_from_line() {
|
||||
local language="$1"
|
||||
local line="$2"
|
||||
|
||||
case "$language" in
|
||||
PHP)
|
||||
printf '%s' "$line" | sed -E 's/.*function[[:space:]]+([A-Za-z_][A-Za-z0-9_]*)[[:space:]]*\(.*/\1/'
|
||||
;;
|
||||
Python)
|
||||
printf '%s' "$line" | sed -E 's/.*def[[:space:]]+([A-Za-z_][A-Za-z0-9_]*)[[:space:]]*\(.*/\1/'
|
||||
;;
|
||||
JavaScript|TypeScript)
|
||||
printf '%s' "$line" | sed -E 's/.*function[[:space:]]+([A-Za-z_$][A-Za-z0-9_$]*)[[:space:]]*\(.*/\1/; s/.*(const|let|var)[[:space:]]+([A-Za-z_$][A-Za-z0-9_$]*)[[:space:]]*=.*/\2/; s/^[[:space:]]*(public|private|protected)?[[:space:]]*(async[[:space:]]+)?([A-Za-z_$][A-Za-z0-9_$]*)[[:space:]]*\(.*/\3/'
|
||||
;;
|
||||
C#|C/C++)
|
||||
printf '%s' "$line" | sed -E 's/.*[[:space:]]+([A-Za-z_][A-Za-z0-9_]*)[[:space:]]*\([^;]*\).*/\1/'
|
||||
;;
|
||||
Ruby)
|
||||
printf '%s' "$line" | sed -E 's/.*def[[:space:]]+([A-Za-z_][A-Za-z0-9_!?=]*).*/\1/'
|
||||
;;
|
||||
Perl)
|
||||
printf '%s' "$line" | sed -E 's/.*sub[[:space:]]+([A-Za-z_][A-Za-z0-9_]*).*/\1/'
|
||||
;;
|
||||
Shell)
|
||||
printf '%s' "$line" | sed -E 's/^[[:space:]]*function[[:space:]]+([A-Za-z_][A-Za-z0-9_]*).*/\1/; s/^[[:space:]]*([A-Za-z_][A-Za-z0-9_]*)[[:space:]]*\(\).*/\1/'
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
return_type_from_line() {
|
||||
local language="$1"
|
||||
local line="$2"
|
||||
|
||||
case "$language" in
|
||||
C#)
|
||||
printf '%s' "$line" | sed -E 's/^[[:space:]]*(public|private|protected|internal)?[[:space:]]*(static[[:space:]]+)?(async[[:space:]]+)?([A-Za-z_][A-Za-z0-9_<>,\[\]\?]*)[[:space:]]+[A-Za-z_][A-Za-z0-9_]*[[:space:]]*\(.*/\4/'
|
||||
;;
|
||||
TypeScript)
|
||||
if [[ "$line" =~ \)[[:space:]]*:[[:space:]]*([^=\{]+) ]]; then
|
||||
printf '%s' "${BASH_REMATCH[1]}" | sed 's/[[:space:]]*$//'
|
||||
else
|
||||
printf 'not detected'
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
printf 'not detected'
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
parameters_from_line() {
|
||||
local line="$1"
|
||||
printf '%s' "$line" | sed -E 's/^[^(]*\(([^)]*)\).*/\1/; t; s/.*/not detected/'
|
||||
}
|
||||
|
||||
visibility_from_line() {
|
||||
local line="$1"
|
||||
if [[ "$line" =~ public ]]; then
|
||||
printf 'public'
|
||||
elif [[ "$line" =~ protected ]]; then
|
||||
printf 'protected'
|
||||
elif [[ "$line" =~ private ]]; then
|
||||
printf 'private'
|
||||
else
|
||||
printf 'not detected'
|
||||
fi
|
||||
}
|
||||
|
||||
static_async_from_line() {
|
||||
local line="$1"
|
||||
local flags=()
|
||||
[[ "$line" =~ static ]] && flags+=("static")
|
||||
[[ "$line" =~ async ]] && flags+=("async")
|
||||
if ((${#flags[@]} == 0)); then
|
||||
printf 'not detected'
|
||||
else
|
||||
printf '%s' "${flags[*]}"
|
||||
fi
|
||||
}
|
||||
|
||||
guess_function_purpose() {
|
||||
local name="$1"
|
||||
local lower
|
||||
lower="$(printf '%s' "$name" | tr '[:upper:]' '[:lower:]')"
|
||||
|
||||
case "$lower" in
|
||||
get*|fetch*|load*) printf 'Likely reads or retrieves data' ;;
|
||||
set*|save*|store*) printf 'Likely writes or updates data' ;;
|
||||
render*|show*|display*) printf 'Likely renders or displays output' ;;
|
||||
handle*|process*) printf 'Likely handles a request, event, or workflow' ;;
|
||||
validate*|check*) printf 'Likely validates data or state' ;;
|
||||
start*|init*|awake) printf 'Likely initialization or startup behavior' ;;
|
||||
update|fixedupdate|lateupdate) printf 'Unity runtime update hook' ;;
|
||||
destroy*|delete*|remove*) printf 'Likely deletion or cleanup behavior' ;;
|
||||
*) printf 'Purpose guessed from name only' ;;
|
||||
esac
|
||||
}
|
||||
|
||||
write_api_reference() {
|
||||
{
|
||||
printf '# API Reference\n\n'
|
||||
printf 'Parsing is best-effort. Dynamic/runtime APIs and complex signatures may not be detected.\n\n'
|
||||
printf '| File | Language | Class | Function/Method | Parameters | Return Type | Visibility | Static/Async | Guessed Purpose | Notes |\n'
|
||||
printf '|---|---|---|---|---|---|---|---|---|---|\n'
|
||||
|
||||
local file language rel classes functions line_no signature name params return_type visibility flags class_list purpose
|
||||
for file in "${SOURCE_FILES[@]}"; do
|
||||
language="$(language_for_file "$file")"
|
||||
rel="$(relative_path "$file")"
|
||||
class_list="$(extract_classes "$file" | join_names)"
|
||||
[[ -z "$class_list" ]] && class_list="not detected"
|
||||
|
||||
while IFS= read -r fn_line; do
|
||||
[[ -z "$fn_line" ]] && continue
|
||||
line_no="${fn_line%%:*}"
|
||||
signature="${fn_line#*:}"
|
||||
name="$(function_name_from_line "$language" "$signature")"
|
||||
[[ -z "$name" || "$name" == "$signature" ]] && name="not detected"
|
||||
params="$(parameters_from_line "$signature")"
|
||||
return_type="$(return_type_from_line "$language" "$signature")"
|
||||
visibility="$(visibility_from_line "$signature")"
|
||||
flags="$(static_async_from_line "$signature")"
|
||||
purpose="$(guess_function_purpose "$name")"
|
||||
FUNCTION_COUNT=$((FUNCTION_COUNT + 1))
|
||||
printf '| `%s` | %s | %s | %s | `%s` | %s | %s | %s | %s | Detected function near line %s. |\n' "$rel" "$language" "$class_list" "$name" "$params" "$return_type" "$visibility" "$flags" "$purpose" "$line_no"
|
||||
done < <(extract_functions "$file")
|
||||
|
||||
local class_name
|
||||
while IFS= read -r class_name; do
|
||||
[[ -z "$class_name" ]] && continue
|
||||
CLASS_COUNT=$((CLASS_COUNT + 1))
|
||||
printf '| `%s` | %s | %s | class declaration | `not applicable` | not applicable | not detected | not detected | Possible class or type definition. | Signature appears to be class-like. |\n' "$rel" "$language" "$class_name"
|
||||
done < <(extract_classes "$file")
|
||||
done
|
||||
} >"$API_REPORT"
|
||||
|
||||
record_generated_file "$API_REPORT"
|
||||
}
|
||||
|
||||
write_file_purpose() {
|
||||
{
|
||||
printf '# File Purpose Report\n\n'
|
||||
printf 'This report uses cautious, best-effort guesses based on filenames, folders, imports, classes, and functions.\n\n'
|
||||
printf '| File | Language/Type | Lines | Detected Classes | Detected Functions/Methods | Imports/Includes/Usings | Possible Purpose | Notes |\n'
|
||||
printf '|---|---|---:|---|---|---|---|---|\n'
|
||||
|
||||
local file rel language lines classes functions imports purpose
|
||||
for file in "${SOURCE_FILES[@]}"; do
|
||||
rel="$(relative_path "$file")"
|
||||
language="$(language_for_file "$file")"
|
||||
lines="$(line_count "$file")"
|
||||
classes="$(extract_classes "$file" | join_names)"
|
||||
functions="$(extract_functions "$file" | sed -E 's/^[0-9]+://' | while IFS= read -r line; do function_name_from_line "$language" "$line"; done | join_names)"
|
||||
imports="$(extract_imports "$file" | join_names)"
|
||||
purpose="$(guess_purpose "$file")"
|
||||
[[ -z "$classes" ]] && classes="none detected"
|
||||
[[ -z "$functions" ]] && functions="none detected"
|
||||
[[ -z "$imports" ]] && imports="none detected"
|
||||
printf '| `%s` | %s | %s | %s | %s | %s | %s | Appears to be best-effort only. |\n' "$rel" "$language" "$lines" "$classes" "$functions" "$imports" "$purpose"
|
||||
done
|
||||
} >"$FILE_PURPOSE_REPORT"
|
||||
|
||||
record_generated_file "$FILE_PURPOSE_REPORT"
|
||||
}
|
||||
|
||||
unity_role_guess() {
|
||||
local file="$1"
|
||||
local content_lower
|
||||
content_lower="$(tr '[:upper:]' '[:lower:]' <"$file")"
|
||||
|
||||
if grep -qi 'gamemanager\|game manager' <<<"$content_lower"; then
|
||||
printf 'Game manager'
|
||||
elif grep -qi 'player' <<<"$content_lower"; then
|
||||
printf 'Player controller'
|
||||
elif grep -qi 'enemy\|ai' <<<"$content_lower"; then
|
||||
printf 'Enemy AI'
|
||||
elif grep -qi 'spawner\|spawn' <<<"$content_lower"; then
|
||||
printf 'Spawner'
|
||||
elif grep -qi 'health\|damage' <<<"$content_lower"; then
|
||||
printf 'Health/damage system'
|
||||
elif grep -qi 'camera' <<<"$content_lower"; then
|
||||
printf 'Camera controller'
|
||||
elif grep -qi 'audio' <<<"$content_lower"; then
|
||||
printf 'Audio manager'
|
||||
elif grep -qi 'scene' <<<"$content_lower"; then
|
||||
printf 'Scene loader'
|
||||
elif grep -qi 'inventory' <<<"$content_lower"; then
|
||||
printf 'Inventory system'
|
||||
elif grep -qi 'ui\|canvas\|textmesh' <<<"$content_lower"; then
|
||||
printf 'UI controller'
|
||||
else
|
||||
printf 'Utility script'
|
||||
fi
|
||||
}
|
||||
|
||||
write_unity_scripts() {
|
||||
local found=0
|
||||
|
||||
{
|
||||
printf '# Unity Script Lifecycle Map\n\n'
|
||||
printf 'This report scans C# files for MonoBehaviour, ScriptableObject, common Unity lifecycle methods, serialized fields, and Unity API usage.\n\n'
|
||||
|
||||
local file rel classes base_classes lifecycle fields public_methods unity_api role class_name
|
||||
for file in "${SOURCE_FILES[@]}"; do
|
||||
[[ "$file" != *.cs ]] && continue
|
||||
grep -Eq 'MonoBehaviour|ScriptableObject' "$file" || continue
|
||||
|
||||
found=1
|
||||
UNITY_SCRIPT_COUNT=$((UNITY_SCRIPT_COUNT + 1))
|
||||
rel="$(relative_path "$file")"
|
||||
classes="$(extract_classes "$file" | join_names)"
|
||||
base_classes="$(grep -Eoh 'class[[:space:]]+[A-Za-z_][A-Za-z0-9_]*[[:space:]]*:[[:space:]]*[A-Za-z_][A-Za-z0-9_]*' "$file" | sed -E 's/.*:[[:space:]]*//' | join_names)"
|
||||
lifecycle="$(grep -Eoh '\b(Awake|Start|Update|FixedUpdate|LateUpdate|OnEnable|OnDisable|OnDestroy|OnTriggerEnter|OnTriggerExit|OnCollisionEnter|OnCollisionExit|OnMouseDown|OnMouseUp)[[:space:]]*\(' "$file" | sed 's/[[:space:]]*(//' | join_names)"
|
||||
fields="$(grep -Eoh '(\[SerializeField\][[:space:]]*)?(public|private|protected)?[[:space:]]*(readonly[[:space:]]+)?[A-Za-z_][A-Za-z0-9_<>,\[\]]*[[:space:]]+[A-Za-z_][A-Za-z0-9_]*[[:space:]]*(=|;)' "$file" | sed -E 's/[=;].*//; s/^[[:space:]]+|[[:space:]]+$//g' | head -20 | join_names)"
|
||||
public_methods="$(grep -Eoh 'public[[:space:]]+[A-Za-z_][A-Za-z0-9_<>,\[\]\?]*[[:space:]]+[A-Za-z_][A-Za-z0-9_]*[[:space:]]*\(' "$file" | sed -E 's/.*[[:space:]]+([A-Za-z_][A-Za-z0-9_]*)[[:space:]]*\(/\1/' | join_names)"
|
||||
unity_api="$(grep -Eoh 'UnityEvent|GetComponent|FindObjectOfType|FindFirstObjectByType|Instantiate|Destroy|SceneManager|Input\.' "$file" | join_names)"
|
||||
role="$(unity_role_guess "$file")"
|
||||
[[ -z "$classes" ]] && classes="not detected"
|
||||
[[ -z "$base_classes" ]] && base_classes="not detected"
|
||||
[[ -z "$lifecycle" ]] && lifecycle="none detected"
|
||||
[[ -z "$fields" ]] && fields="none detected"
|
||||
[[ -z "$public_methods" ]] && public_methods="none detected"
|
||||
[[ -z "$unity_api" ]] && unity_api="none detected"
|
||||
|
||||
printf '## `%s`\n\n' "$rel"
|
||||
printf -- '- Class name: %s\n' "$classes"
|
||||
printf -- '- Base class: %s\n' "$base_classes"
|
||||
printf -- '- Lifecycle methods detected: %s\n' "$lifecycle"
|
||||
printf -- '- Serialized/public fields: %s\n' "$fields"
|
||||
printf -- '- Public methods: %s\n' "$public_methods"
|
||||
printf -- '- Unity API usage: %s\n' "$unity_api"
|
||||
printf -- '- Possible script role: %s\n\n' "$role"
|
||||
done
|
||||
|
||||
if [[ "$found" -eq 0 ]]; then
|
||||
printf 'No Unity MonoBehaviour or ScriptableObject scripts detected.\n'
|
||||
fi
|
||||
} >"$UNITY_REPORT"
|
||||
|
||||
record_generated_file "$UNITY_REPORT"
|
||||
}
|
||||
|
||||
extract_called_names() {
|
||||
local file="$1"
|
||||
grep -Eoh '\b[A-Za-z_][A-Za-z0-9_]*[[:space:]]*\(' "$file" | sed -E 's/[[:space:]]*\($//' | grep -Ev '^(if|for|while|switch|catch|function|def|class|return|echo|print|new|sizeof)$' | join_names
|
||||
}
|
||||
|
||||
write_call_map() {
|
||||
{
|
||||
printf '# Call Map\n\n'
|
||||
printf 'Best-effort static scan. Dynamic/runtime calls may not be detected.\n\n'
|
||||
|
||||
printf '## Possible Entry Points\n\n'
|
||||
local file rel language entry_found=0
|
||||
for file in "${SOURCE_FILES[@]}"; do
|
||||
if is_entry_point "$file"; then
|
||||
rel="$(relative_path "$file")"
|
||||
printf -- '- `%s`\n' "$rel"
|
||||
ENTRY_POINT_COUNT=$((ENTRY_POINT_COUNT + 1))
|
||||
entry_found=1
|
||||
fi
|
||||
done
|
||||
[[ "$entry_found" -eq 0 ]] && printf -- '- No obvious entry points detected.\n'
|
||||
|
||||
printf '\n## Detected Imports/Includes\n\n'
|
||||
for file in "${SOURCE_FILES[@]}"; do
|
||||
rel="$(relative_path "$file")"
|
||||
local imports
|
||||
imports="$(extract_imports "$file" | join_names)"
|
||||
[[ -n "$imports" ]] && printf -- '- `%s`: %s\n' "$rel" "$imports"
|
||||
done
|
||||
|
||||
printf '\n## Function Definitions\n\n'
|
||||
for file in "${SOURCE_FILES[@]}"; do
|
||||
rel="$(relative_path "$file")"
|
||||
language="$(language_for_file "$file")"
|
||||
local functions
|
||||
functions="$(extract_functions "$file" | sed -E 's/^[0-9]+://' | while IFS= read -r line; do function_name_from_line "$language" "$line"; done | join_names)"
|
||||
[[ -n "$functions" ]] && printf -- '- `%s`: %s\n' "$rel" "$functions"
|
||||
done
|
||||
|
||||
printf '\n## Function Call References\n\n'
|
||||
for file in "${SOURCE_FILES[@]}"; do
|
||||
rel="$(relative_path "$file")"
|
||||
local calls
|
||||
calls="$(extract_called_names "$file")"
|
||||
[[ -n "$calls" ]] && printf -- '- `%s`: possible calls to %s\n' "$rel" "$calls"
|
||||
done
|
||||
|
||||
printf '\n## Possible Call Relationships\n\n'
|
||||
for file in "${SOURCE_FILES[@]}"; do
|
||||
rel="$(relative_path "$file")"
|
||||
local imports
|
||||
imports="$(extract_imports "$file" | join_names)"
|
||||
if [[ -n "$imports" ]]; then
|
||||
printf -- '- `%s` may depend on imported or included code: %s\n' "$rel" "$imports"
|
||||
fi
|
||||
done
|
||||
|
||||
printf '\n## Limitations\n\n'
|
||||
printf -- '- Best-effort static scan only.\n'
|
||||
printf -- '- Runtime reflection, framework routing, dependency injection, dynamic imports, and generated code may not be detected.\n'
|
||||
printf -- '- Unity lifecycle methods are entry-like runtime hooks, not direct command-line entry points.\n'
|
||||
} >"$CALL_MAP_REPORT"
|
||||
|
||||
record_generated_file "$CALL_MAP_REPORT"
|
||||
}
|
||||
|
||||
write_generated_files_report() {
|
||||
if [[ -f "$GENERATED_REPORT" ]]; then
|
||||
while IFS= read -r file; do
|
||||
[[ -z "$file" ]] && continue
|
||||
[[ "$file" == \#* ]] && continue
|
||||
[[ "$file" == "- "* ]] || continue
|
||||
record_generated_file "$ROOT_DIR/${file#- }"
|
||||
done <"$GENERATED_REPORT"
|
||||
fi
|
||||
|
||||
record_generated_file "$GENERATED_REPORT"
|
||||
|
||||
{
|
||||
printf '# Generated Files\n\n'
|
||||
local file
|
||||
for file in "${GENERATED_FILES[@]}"; do
|
||||
printf -- '- %s\n' "$file"
|
||||
done
|
||||
} >"$GENERATED_REPORT"
|
||||
}
|
||||
|
||||
write_summary() {
|
||||
record_generated_file "$SUMMARY_REPORT"
|
||||
|
||||
{
|
||||
printf '# Code Documentation Summary\n\n'
|
||||
printf '===== CODE DOCUMENTATION SUMMARY =====\n\n'
|
||||
printf 'Reports written:\n'
|
||||
printf -- '- reports/api-reference.md\n'
|
||||
printf -- '- reports/file-purpose.md\n'
|
||||
printf -- '- reports/unity-scripts.md\n'
|
||||
printf -- '- reports/call-map.md\n'
|
||||
printf -- '- reports/code-docs-summary.md\n\n'
|
||||
printf 'Detected:\n'
|
||||
printf -- '- Source files scanned: %s\n' "$SOURCE_COUNT"
|
||||
printf -- '- Functions/methods detected: %s\n' "$FUNCTION_COUNT"
|
||||
printf -- '- Classes detected: %s\n' "$CLASS_COUNT"
|
||||
printf -- '- Unity scripts detected: %s\n' "$UNITY_SCRIPT_COUNT"
|
||||
printf -- '- Possible entry points detected: %s\n' "$ENTRY_POINT_COUNT"
|
||||
printf '\nDetected code types:\n'
|
||||
if ((${#DETECTED_CODE_TYPES[@]} == 0)); then
|
||||
printf -- '- none detected\n'
|
||||
else
|
||||
local type
|
||||
for type in "${DETECTED_CODE_TYPES[@]}"; do
|
||||
printf -- '- %s\n' "$type"
|
||||
done
|
||||
fi
|
||||
} >"$SUMMARY_REPORT"
|
||||
}
|
||||
|
||||
print_summary() {
|
||||
printf '===== CODE DOCUMENTATION SUMMARY =====\n'
|
||||
printf 'Reports written:\n'
|
||||
printf -- '- reports/api-reference.md\n'
|
||||
printf -- '- reports/file-purpose.md\n'
|
||||
printf -- '- reports/unity-scripts.md\n'
|
||||
printf -- '- reports/call-map.md\n'
|
||||
printf -- '- reports/code-docs-summary.md\n'
|
||||
printf 'Detected:\n'
|
||||
printf -- '- Source files scanned: %s\n' "$SOURCE_COUNT"
|
||||
printf -- '- Functions/methods detected: %s\n' "$FUNCTION_COUNT"
|
||||
printf -- '- Classes detected: %s\n' "$CLASS_COUNT"
|
||||
printf -- '- Unity scripts detected: %s\n' "$UNITY_SCRIPT_COUNT"
|
||||
printf -- '- Possible entry points detected: %s\n' "$ENTRY_POINT_COUNT"
|
||||
printf 'Detected code types:\n'
|
||||
local type
|
||||
if ((${#DETECTED_CODE_TYPES[@]} == 0)); then
|
||||
printf -- '- none detected\n'
|
||||
else
|
||||
for type in "${DETECTED_CODE_TYPES[@]}"; do
|
||||
printf -- '- %s\n' "$type"
|
||||
done
|
||||
fi
|
||||
}
|
||||
|
||||
main() {
|
||||
collect_source_files
|
||||
SOURCE_COUNT="${#SOURCE_FILES[@]}"
|
||||
|
||||
local file language
|
||||
for file in "${SOURCE_FILES[@]}"; do
|
||||
language="$(language_for_file "$file")"
|
||||
append_detected_type "$language"
|
||||
if [[ "$language" == "C#" ]] && grep -Eq 'MonoBehaviour|ScriptableObject' "$file"; then
|
||||
append_detected_type "Unity C# scripts"
|
||||
fi
|
||||
done
|
||||
|
||||
write_api_reference || exit 1
|
||||
write_file_purpose || exit 1
|
||||
write_unity_scripts || exit 1
|
||||
write_call_map || exit 1
|
||||
write_summary || exit 1
|
||||
write_generated_files_report || exit 1
|
||||
print_summary
|
||||
}
|
||||
|
||||
main "$@"
|
||||
586
tools/project-report.sh
Normal file
586
tools/project-report.sh
Normal file
|
|
@ -0,0 +1,586 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -u -o pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
REPORT_DIR="$ROOT_DIR/reports"
|
||||
FLOW_REPORT="$REPORT_DIR/project-flow.md"
|
||||
FILE_REPORT="$REPORT_DIR/file-summary.md"
|
||||
LANG_REPORT="$REPORT_DIR/languages.md"
|
||||
SUMMARY_REPORT="$REPORT_DIR/summary.md"
|
||||
GENERATED_REPORT="$REPORT_DIR/generated-files.md"
|
||||
STATUS_FILE="$REPORT_DIR/customer-code-status.txt"
|
||||
ERROR_REPORT="$REPORT_DIR/errors.md"
|
||||
|
||||
EXCLUDES=(
|
||||
".git"
|
||||
".forgejo"
|
||||
"node_modules"
|
||||
"vendor"
|
||||
"Library"
|
||||
"Temp"
|
||||
"Logs"
|
||||
"obj"
|
||||
"bin"
|
||||
)
|
||||
|
||||
GENERATED_FILES=()
|
||||
|
||||
declare -A LANGUAGE_COUNTS=(
|
||||
["PHP"]=0
|
||||
["JavaScript"]=0
|
||||
["TypeScript"]=0
|
||||
["Python"]=0
|
||||
["C#"]=0
|
||||
["C++"]=0
|
||||
["Ruby"]=0
|
||||
["Perl"]=0
|
||||
["HTML"]=0
|
||||
["CSS"]=0
|
||||
["Shell"]=0
|
||||
["JSON"]=0
|
||||
["YAML"]=0
|
||||
)
|
||||
|
||||
ENTRY_POINTS=()
|
||||
STARTUP_SCRIPTS=()
|
||||
TOP_LEVEL_DIRS=()
|
||||
ENV_FILES=()
|
||||
CONFIG_FILES=()
|
||||
DOCKER_FILES=()
|
||||
DB_CONFIGS=()
|
||||
|
||||
have_tool() {
|
||||
command -v "$1" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
record_generated_file() {
|
||||
local file="$1"
|
||||
local rel="${file#"$ROOT_DIR"/}"
|
||||
local existing
|
||||
|
||||
for existing in "${GENERATED_FILES[@]}"; do
|
||||
if [[ "$existing" == "$rel" ]]; then
|
||||
return
|
||||
fi
|
||||
done
|
||||
|
||||
GENERATED_FILES+=("$rel")
|
||||
}
|
||||
|
||||
relative_path() {
|
||||
local path="$1"
|
||||
path="${path#"$ROOT_DIR"/}"
|
||||
printf '%s' "$path"
|
||||
}
|
||||
|
||||
ensure_report_dir() {
|
||||
mkdir -p "$REPORT_DIR"
|
||||
}
|
||||
|
||||
collect_files() {
|
||||
local find_args=("$ROOT_DIR")
|
||||
local exclude
|
||||
|
||||
if ((${#EXCLUDES[@]} > 0)); then
|
||||
find_args+=("(")
|
||||
for exclude in "${EXCLUDES[@]}"; do
|
||||
find_args+=(-name "$exclude" -o)
|
||||
done
|
||||
unset 'find_args[${#find_args[@]}-1]'
|
||||
find_args+=(")" -prune -o)
|
||||
fi
|
||||
|
||||
find_args+=(-type f -print0)
|
||||
find "${find_args[@]}"
|
||||
}
|
||||
|
||||
search_tree() {
|
||||
local pattern="$1"
|
||||
if have_tool rg; then
|
||||
rg -qi -e "$pattern" "$ROOT_DIR" \
|
||||
--glob '!.git/**' \
|
||||
--glob '!.forgejo/**' \
|
||||
--glob '!node_modules/**' \
|
||||
--glob '!vendor/**' \
|
||||
--glob '!Library/**' \
|
||||
--glob '!Temp/**' \
|
||||
--glob '!Logs/**' \
|
||||
--glob '!obj/**' \
|
||||
--glob '!bin/**'
|
||||
else
|
||||
grep -RqiE \
|
||||
--exclude-dir=.git \
|
||||
--exclude-dir=.forgejo \
|
||||
--exclude-dir=node_modules \
|
||||
--exclude-dir=vendor \
|
||||
--exclude-dir=Library \
|
||||
--exclude-dir=Temp \
|
||||
--exclude-dir=Logs \
|
||||
--exclude-dir=obj \
|
||||
--exclude-dir=bin \
|
||||
"$pattern" "$ROOT_DIR"
|
||||
fi
|
||||
}
|
||||
|
||||
append_unique() {
|
||||
local value="$1"
|
||||
shift
|
||||
local existing
|
||||
|
||||
for existing in "$@"; do
|
||||
if [[ "$existing" == "$value" ]]; then
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
detect_entry_or_startup() {
|
||||
local file="$1"
|
||||
local base
|
||||
local rel
|
||||
|
||||
base="$(basename "$file")"
|
||||
rel="$(relative_path "$file")"
|
||||
|
||||
case "$base" in
|
||||
index.php|index.html|index.htm|main.py|app.py|server.py|Program.cs|Main.cs|main.cpp|main.c)
|
||||
ENTRY_POINTS+=("$rel")
|
||||
;;
|
||||
start*.sh|launch*.sh|run*.sh|startup*.sh|*.bat|*.cmd)
|
||||
STARTUP_SCRIPTS+=("$rel")
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
detect_config_files() {
|
||||
local file="$1"
|
||||
local rel
|
||||
|
||||
rel="$(relative_path "$file")"
|
||||
|
||||
case "$(basename "$file")" in
|
||||
.env|.env.*)
|
||||
ENV_FILES+=("$rel")
|
||||
;;
|
||||
Dockerfile|docker-compose.yml|docker-compose.yaml|compose.yml|compose.yaml)
|
||||
DOCKER_FILES+=("$rel")
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$file" in
|
||||
*.conf|*.config|*.ini|*.env|*.yaml|*.yml|*.json|*config*.php|*config*.py|*config*.rb|*settings*.php)
|
||||
CONFIG_FILES+=("$rel")
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$file" in
|
||||
*database*|*db*config*|*mysql*|*pgsql*|*postgres*|*sqlite*|*mongo*)
|
||||
DB_CONFIGS+=("$rel")
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
classify_language() {
|
||||
local file="$1"
|
||||
case "$file" in
|
||||
*.php) LANGUAGE_COUNTS["PHP"]=$((LANGUAGE_COUNTS["PHP"] + 1)) ;;
|
||||
*.js|*.mjs|*.cjs) LANGUAGE_COUNTS["JavaScript"]=$((LANGUAGE_COUNTS["JavaScript"] + 1)) ;;
|
||||
*.ts|*.tsx) LANGUAGE_COUNTS["TypeScript"]=$((LANGUAGE_COUNTS["TypeScript"] + 1)) ;;
|
||||
*.py) LANGUAGE_COUNTS["Python"]=$((LANGUAGE_COUNTS["Python"] + 1)) ;;
|
||||
*.cs) LANGUAGE_COUNTS["C#"]=$((LANGUAGE_COUNTS["C#"] + 1)) ;;
|
||||
*.cpp|*.cc|*.cxx|*.c|*.h|*.hpp) LANGUAGE_COUNTS["C++"]=$((LANGUAGE_COUNTS["C++"] + 1)) ;;
|
||||
*.rb) LANGUAGE_COUNTS["Ruby"]=$((LANGUAGE_COUNTS["Ruby"] + 1)) ;;
|
||||
*.pl|*.pm) LANGUAGE_COUNTS["Perl"]=$((LANGUAGE_COUNTS["Perl"] + 1)) ;;
|
||||
*.html|*.htm) LANGUAGE_COUNTS["HTML"]=$((LANGUAGE_COUNTS["HTML"] + 1)) ;;
|
||||
*.css) LANGUAGE_COUNTS["CSS"]=$((LANGUAGE_COUNTS["CSS"] + 1)) ;;
|
||||
*.sh|*.bash) LANGUAGE_COUNTS["Shell"]=$((LANGUAGE_COUNTS["Shell"] + 1)) ;;
|
||||
*.json) LANGUAGE_COUNTS["JSON"]=$((LANGUAGE_COUNTS["JSON"] + 1)) ;;
|
||||
*.yml|*.yaml) LANGUAGE_COUNTS["YAML"]=$((LANGUAGE_COUNTS["YAML"] + 1)) ;;
|
||||
esac
|
||||
}
|
||||
|
||||
detect_frameworks() {
|
||||
local frameworks=()
|
||||
|
||||
[[ -f "$ROOT_DIR/artisan" ]] && frameworks+=("Laravel")
|
||||
[[ -d "$ROOT_DIR/app/Config" ]] && frameworks+=("CodeIgniter")
|
||||
[[ -f "$ROOT_DIR/wp-config.php" ]] && frameworks+=("WordPress")
|
||||
[[ -f "$ROOT_DIR/manage.py" ]] && frameworks+=("Django")
|
||||
[[ -f "$ROOT_DIR/app.py" ]] && search_tree "Flask" && frameworks+=("Flask")
|
||||
[[ -f "$ROOT_DIR/package.json" ]] && frameworks+=("Node.js")
|
||||
[[ -f "$ROOT_DIR/package.json" ]] && search_tree "\"express\"" && frameworks+=("Express")
|
||||
[[ -f "$ROOT_DIR/package.json" ]] && search_tree "\"react\"" && frameworks+=("React")
|
||||
[[ -f "$ROOT_DIR/package.json" ]] && search_tree "\"vue\"" && frameworks+=("Vue")
|
||||
find "$ROOT_DIR" -path "$ROOT_DIR/.git" -prune -o -name '*.csproj' -type f -print | grep -q . 2>/dev/null && frameworks+=("ASP.NET")
|
||||
find "$ROOT_DIR" -path "$ROOT_DIR/.git" -prune -o -name 'ProjectSettings.asset' -type f -print | grep -q . 2>/dev/null && frameworks+=("Unity")
|
||||
[[ -d "$ROOT_DIR/src/Symfony" || -f "$ROOT_DIR/bin/console" ]] && frameworks+=("Symfony")
|
||||
|
||||
printf '%s\n' "${frameworks[@]}" | awk 'NF && !seen[$0]++'
|
||||
}
|
||||
|
||||
detect_databases() {
|
||||
local databases=()
|
||||
|
||||
search_tree "mysql" && databases+=("MySQL")
|
||||
search_tree "mariadb" && databases+=("MariaDB")
|
||||
search_tree "postgres|postgresql|pgsql" && databases+=("PostgreSQL")
|
||||
search_tree "sqlite" && databases+=("SQLite")
|
||||
search_tree "mongodb|mongo" && databases+=("MongoDB")
|
||||
|
||||
printf '%s\n' "${databases[@]}" | awk 'NF && !seen[$0]++'
|
||||
}
|
||||
|
||||
read_customer_status() {
|
||||
if [[ -f "$STATUS_FILE" ]]; then
|
||||
head -n 1 "$STATUS_FILE"
|
||||
elif [[ -f "$ERROR_REPORT" ]] && rg -q "CUSTOMER CODE ERRORS FOUND|NO CUSTOMER CODE ERRORS FOUND" "$ERROR_REPORT"; then
|
||||
rg -m 1 "CUSTOMER CODE ERRORS FOUND|NO CUSTOMER CODE ERRORS FOUND" "$ERROR_REPORT"
|
||||
else
|
||||
printf 'CUSTOMER CODE STATUS UNKNOWN\n'
|
||||
fi
|
||||
}
|
||||
|
||||
detect_repo_name() {
|
||||
basename "$ROOT_DIR"
|
||||
}
|
||||
|
||||
detect_branch_name() {
|
||||
if have_tool git && git -C "$ROOT_DIR" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
||||
git -C "$ROOT_DIR" rev-parse --abbrev-ref HEAD 2>/dev/null || printf 'unknown'
|
||||
else
|
||||
printf 'unknown'
|
||||
fi
|
||||
}
|
||||
|
||||
detect_commit_hash() {
|
||||
if have_tool git && git -C "$ROOT_DIR" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
||||
git -C "$ROOT_DIR" rev-parse --short HEAD 2>/dev/null || printf 'unknown'
|
||||
else
|
||||
printf 'unknown'
|
||||
fi
|
||||
}
|
||||
|
||||
write_languages_report() {
|
||||
{
|
||||
printf '# Languages\n\n'
|
||||
printf '| Language | Count |\n'
|
||||
printf '|---|---:|\n'
|
||||
printf '| PHP | %s |\n' "${LANGUAGE_COUNTS["PHP"]}"
|
||||
printf '| JavaScript | %s |\n' "${LANGUAGE_COUNTS["JavaScript"]}"
|
||||
printf '| TypeScript | %s |\n' "${LANGUAGE_COUNTS["TypeScript"]}"
|
||||
printf '| Python | %s |\n' "${LANGUAGE_COUNTS["Python"]}"
|
||||
printf '| C# | %s |\n' "${LANGUAGE_COUNTS["C#"]}"
|
||||
printf '| C++ | %s |\n' "${LANGUAGE_COUNTS["C++"]}"
|
||||
printf '| Ruby | %s |\n' "${LANGUAGE_COUNTS["Ruby"]}"
|
||||
printf '| Perl | %s |\n' "${LANGUAGE_COUNTS["Perl"]}"
|
||||
printf '| HTML | %s |\n' "${LANGUAGE_COUNTS["HTML"]}"
|
||||
printf '| CSS | %s |\n' "${LANGUAGE_COUNTS["CSS"]}"
|
||||
printf '| Shell | %s |\n' "${LANGUAGE_COUNTS["Shell"]}"
|
||||
printf '| JSON | %s |\n' "${LANGUAGE_COUNTS["JSON"]}"
|
||||
printf '| YAML | %s |\n' "${LANGUAGE_COUNTS["YAML"]}"
|
||||
} >"$LANG_REPORT"
|
||||
|
||||
record_generated_file "$LANG_REPORT"
|
||||
}
|
||||
|
||||
write_file_summary_report() {
|
||||
{
|
||||
printf '# File Summary\n\n'
|
||||
printf '## Top-Level Folders\n\n'
|
||||
if ((${#TOP_LEVEL_DIRS[@]} == 0)); then
|
||||
printf -- '- None detected\n'
|
||||
else
|
||||
local dir
|
||||
for dir in "${TOP_LEVEL_DIRS[@]}"; do
|
||||
printf -- '- %s\n' "$dir"
|
||||
done
|
||||
fi
|
||||
|
||||
printf '\n## File Counts By Type\n\n'
|
||||
printf '| Type | Count |\n'
|
||||
printf '|---|---:|\n'
|
||||
printf '| PHP | %s |\n' "${LANGUAGE_COUNTS["PHP"]}"
|
||||
printf '| JavaScript | %s |\n' "${LANGUAGE_COUNTS["JavaScript"]}"
|
||||
printf '| TypeScript | %s |\n' "${LANGUAGE_COUNTS["TypeScript"]}"
|
||||
printf '| Python | %s |\n' "${LANGUAGE_COUNTS["Python"]}"
|
||||
printf '| C# | %s |\n' "${LANGUAGE_COUNTS["C#"]}"
|
||||
printf '| C++ | %s |\n' "${LANGUAGE_COUNTS["C++"]}"
|
||||
printf '| Ruby | %s |\n' "${LANGUAGE_COUNTS["Ruby"]}"
|
||||
printf '| Perl | %s |\n' "${LANGUAGE_COUNTS["Perl"]}"
|
||||
printf '| HTML | %s |\n' "${LANGUAGE_COUNTS["HTML"]}"
|
||||
printf '| CSS | %s |\n' "${LANGUAGE_COUNTS["CSS"]}"
|
||||
printf '| Shell | %s |\n' "${LANGUAGE_COUNTS["Shell"]}"
|
||||
printf '| JSON | %s |\n' "${LANGUAGE_COUNTS["JSON"]}"
|
||||
printf '| YAML | %s |\n' "${LANGUAGE_COUNTS["YAML"]}"
|
||||
|
||||
printf '\n## Largest Source Directories\n\n'
|
||||
find "$ROOT_DIR" -mindepth 1 -maxdepth 2 -type d \
|
||||
! -path "$ROOT_DIR/.git*" \
|
||||
! -path "$ROOT_DIR/.forgejo*" \
|
||||
-print0 | while IFS= read -r -d '' dir; do
|
||||
count="$(find "$dir" -type f | wc -l | tr -d ' ')"
|
||||
printf '%s\t%s\n' "$count" "$(relative_path "$dir")"
|
||||
done | sort -rn | head -n 10 | awk -F '\t' '{printf "- %s files: %s\n", $1, $2}'
|
||||
} >"$FILE_REPORT"
|
||||
|
||||
record_generated_file "$FILE_REPORT"
|
||||
}
|
||||
|
||||
write_project_flow_report() {
|
||||
local frameworks="$1"
|
||||
local databases="$2"
|
||||
local likely_entry="No obvious entry point detected"
|
||||
|
||||
if ((${#ENTRY_POINTS[@]} > 0)); then
|
||||
likely_entry="${ENTRY_POINTS[0]}"
|
||||
fi
|
||||
|
||||
{
|
||||
printf '# Project Flow Report\n\n'
|
||||
printf '## Likely Entry Point\n\n'
|
||||
printf -- '- Likely entry point: %s\n' "$likely_entry"
|
||||
|
||||
printf '\n## Possible Startup Files\n\n'
|
||||
if ((${#ENTRY_POINTS[@]} == 0 && ${#STARTUP_SCRIPTS[@]} == 0)); then
|
||||
printf -- '- No obvious startup files detected.\n'
|
||||
else
|
||||
local entry
|
||||
for entry in "${ENTRY_POINTS[@]}"; do
|
||||
printf -- '- Possible startup file: %s\n' "$entry"
|
||||
done
|
||||
local script
|
||||
for script in "${STARTUP_SCRIPTS[@]}"; do
|
||||
printf -- '- Possible launch script: %s\n' "$script"
|
||||
done
|
||||
fi
|
||||
|
||||
printf '\n## Detected Frameworks\n\n'
|
||||
if [[ -n "$frameworks" ]]; then
|
||||
printf '%s\n' "$frameworks" | while IFS= read -r item; do
|
||||
[[ -n "$item" ]] && printf -- '- Detected framework: %s\n' "$item"
|
||||
done
|
||||
else
|
||||
printf -- '- No supported framework markers were detected.\n'
|
||||
fi
|
||||
|
||||
printf '\n## Detected Databases\n\n'
|
||||
if [[ -n "$databases" ]]; then
|
||||
printf '%s\n' "$databases" | while IFS= read -r item; do
|
||||
[[ -n "$item" ]] && printf -- '- Possible database usage: %s\n' "$item"
|
||||
done
|
||||
else
|
||||
printf -- '- No database markers were detected.\n'
|
||||
fi
|
||||
|
||||
printf '\n## Configuration Files\n\n'
|
||||
if ((${#ENV_FILES[@]} > 0)); then
|
||||
printf -- '- Possible .env files: %s\n' "${ENV_FILES[*]}"
|
||||
else
|
||||
printf -- '- No .env files detected.\n'
|
||||
fi
|
||||
if ((${#DOCKER_FILES[@]} > 0)); then
|
||||
printf -- '- Docker-related files: %s\n' "${DOCKER_FILES[*]}"
|
||||
else
|
||||
printf -- '- No Docker-related files detected.\n'
|
||||
fi
|
||||
if ((${#DB_CONFIGS[@]} > 0)); then
|
||||
printf -- '- Possible database config files: %s\n' "${DB_CONFIGS[*]}"
|
||||
else
|
||||
printf -- '- No obvious database config files detected.\n'
|
||||
fi
|
||||
|
||||
printf '\n## Important Files To Review\n\n'
|
||||
if ((${#ENTRY_POINTS[@]} > 0)); then
|
||||
local entry
|
||||
for entry in "${ENTRY_POINTS[@]}"; do
|
||||
printf -- '- %s\n' "$entry"
|
||||
done
|
||||
fi
|
||||
if ((${#CONFIG_FILES[@]} > 0)); then
|
||||
local max_items=10
|
||||
local i
|
||||
for ((i = 0; i < ${#CONFIG_FILES[@]} && i < max_items; i++)); do
|
||||
printf -- '- %s\n' "${CONFIG_FILES[$i]}"
|
||||
done
|
||||
else
|
||||
printf -- '- No common config files detected.\n'
|
||||
fi
|
||||
|
||||
printf '\n## Probable Application Flow\n\n'
|
||||
printf 'This is a best-effort analysis only.\n\n'
|
||||
printf -- '- Probable application flow starts near `%s`.\n' "$likely_entry"
|
||||
printf -- '- Likely supporting behavior is configured through nearby config files, environment files, or workflow files.\n'
|
||||
printf -- '- If framework markers are present, startup and routing probably follow that framework'"'"'s conventions.\n'
|
||||
printf -- '- If no framework markers are present, the repository likely uses a simpler file-based startup flow.\n'
|
||||
} >"$FLOW_REPORT"
|
||||
|
||||
record_generated_file "$FLOW_REPORT"
|
||||
}
|
||||
|
||||
write_summary_report() {
|
||||
local customer_status="$1"
|
||||
local frameworks="$2"
|
||||
local databases="$3"
|
||||
local repo_name branch_name commit_hash likely_entry
|
||||
local generated_items=()
|
||||
|
||||
repo_name="$(detect_repo_name)"
|
||||
branch_name="$(detect_branch_name)"
|
||||
commit_hash="$(detect_commit_hash)"
|
||||
likely_entry="No obvious entry point detected"
|
||||
|
||||
if ((${#ENTRY_POINTS[@]} > 0)); then
|
||||
likely_entry="${ENTRY_POINTS[0]}"
|
||||
fi
|
||||
|
||||
generated_items=("${GENERATED_FILES[@]}")
|
||||
if append_unique "reports/summary.md" "${generated_items[@]}"; then
|
||||
generated_items+=("reports/summary.md")
|
||||
fi
|
||||
|
||||
{
|
||||
printf '# Automation Summary\n\n'
|
||||
printf -- '- Generated: %s\n' "$(date '+%Y-%m-%d %H:%M:%S %Z')"
|
||||
printf -- '- Repository: %s\n' "$repo_name"
|
||||
printf -- '- Branch: %s\n' "$branch_name"
|
||||
printf -- '- Commit: %s\n' "$commit_hash"
|
||||
printf -- '- Customer code status: %s\n\n' "$customer_status"
|
||||
|
||||
printf '## Files Created Or Updated By This Run\n\n'
|
||||
local file
|
||||
for file in "${generated_items[@]}"; do
|
||||
printf -- '- %s\n' "$file"
|
||||
done
|
||||
|
||||
printf '\n## Short Project Overview\n\n'
|
||||
printf 'This repository is being analyzed as a customer debugging project. The automation generates validation and structure reports without treating customer code errors as workflow failures.\n'
|
||||
|
||||
printf '\n## Language Counts\n\n'
|
||||
printf -- '- PHP: %s\n' "${LANGUAGE_COUNTS["PHP"]}"
|
||||
printf -- '- JavaScript: %s\n' "${LANGUAGE_COUNTS["JavaScript"]}"
|
||||
printf -- '- TypeScript: %s\n' "${LANGUAGE_COUNTS["TypeScript"]}"
|
||||
printf -- '- Python: %s\n' "${LANGUAGE_COUNTS["Python"]}"
|
||||
printf -- '- C#: %s\n' "${LANGUAGE_COUNTS["C#"]}"
|
||||
printf -- '- C++: %s\n' "${LANGUAGE_COUNTS["C++"]}"
|
||||
printf -- '- Ruby: %s\n' "${LANGUAGE_COUNTS["Ruby"]}"
|
||||
printf -- '- Perl: %s\n' "${LANGUAGE_COUNTS["Perl"]}"
|
||||
printf -- '- HTML: %s\n' "${LANGUAGE_COUNTS["HTML"]}"
|
||||
printf -- '- CSS: %s\n' "${LANGUAGE_COUNTS["CSS"]}"
|
||||
printf -- '- Shell: %s\n' "${LANGUAGE_COUNTS["Shell"]}"
|
||||
printf -- '- JSON: %s\n' "${LANGUAGE_COUNTS["JSON"]}"
|
||||
printf -- '- YAML: %s\n' "${LANGUAGE_COUNTS["YAML"]}"
|
||||
|
||||
printf '\n## Likely Entry Points\n\n'
|
||||
if ((${#ENTRY_POINTS[@]} > 0)); then
|
||||
local entry
|
||||
for entry in "${ENTRY_POINTS[@]}"; do
|
||||
printf -- '- %s\n' "$entry"
|
||||
done
|
||||
else
|
||||
printf -- '- %s\n' "$likely_entry"
|
||||
fi
|
||||
|
||||
printf '\n## Detected Frameworks\n\n'
|
||||
if [[ -n "$frameworks" ]]; then
|
||||
printf '%s\n' "$frameworks" | while IFS= read -r item; do
|
||||
[[ -n "$item" ]] && printf -- '- %s\n' "$item"
|
||||
done
|
||||
else
|
||||
printf -- '- None detected\n'
|
||||
fi
|
||||
|
||||
printf '\n## Detected Databases\n\n'
|
||||
if [[ -n "$databases" ]]; then
|
||||
printf '%s\n' "$databases" | while IFS= read -r item; do
|
||||
[[ -n "$item" ]] && printf -- '- %s\n' "$item"
|
||||
done
|
||||
else
|
||||
printf -- '- None detected\n'
|
||||
fi
|
||||
|
||||
printf '\n## Report Files Generated\n\n'
|
||||
local generated
|
||||
for generated in "${generated_items[@]}"; do
|
||||
printf -- '- %s\n' "$generated"
|
||||
done
|
||||
} >"$SUMMARY_REPORT"
|
||||
|
||||
record_generated_file "$SUMMARY_REPORT"
|
||||
}
|
||||
|
||||
write_generated_files_report() {
|
||||
if [[ -f "$GENERATED_REPORT" ]]; then
|
||||
while IFS= read -r file; do
|
||||
[[ -z "$file" ]] && continue
|
||||
[[ "$file" == \#* ]] && continue
|
||||
[[ "$file" == "- "* ]] || continue
|
||||
file="${file#- }"
|
||||
record_generated_file "$ROOT_DIR/$file"
|
||||
done <"$GENERATED_REPORT"
|
||||
fi
|
||||
|
||||
{
|
||||
printf '# Generated Files\n\n'
|
||||
if ((${#GENERATED_FILES[@]} == 0)); then
|
||||
printf 'No report files were generated.\n'
|
||||
else
|
||||
local file
|
||||
for file in "${GENERATED_FILES[@]}"; do
|
||||
printf -- '- %s\n' "$file"
|
||||
done
|
||||
fi
|
||||
} >"$GENERATED_REPORT"
|
||||
|
||||
record_generated_file "$GENERATED_REPORT"
|
||||
}
|
||||
|
||||
main() {
|
||||
ensure_report_dir || exit 1
|
||||
|
||||
while IFS= read -r -d '' file; do
|
||||
classify_language "$file"
|
||||
detect_entry_or_startup "$file"
|
||||
detect_config_files "$file"
|
||||
done < <(collect_files)
|
||||
|
||||
while IFS= read -r dir; do
|
||||
TOP_LEVEL_DIRS+=("$dir")
|
||||
done < <(find "$ROOT_DIR" -mindepth 1 -maxdepth 1 -type d ! -name '.git' | sed "s#^$ROOT_DIR/##" | sort)
|
||||
|
||||
local frameworks databases customer_status
|
||||
frameworks="$(detect_frameworks)"
|
||||
databases="$(detect_databases)"
|
||||
customer_status="$(read_customer_status)"
|
||||
|
||||
write_languages_report || exit 1
|
||||
write_file_summary_report || exit 1
|
||||
write_project_flow_report "$frameworks" "$databases" || exit 1
|
||||
write_generated_files_report || exit 1
|
||||
write_summary_report "$customer_status" "$frameworks" "$databases" || exit 1
|
||||
write_generated_files_report || exit 1
|
||||
|
||||
printf '===== PROJECT REPORT COMPLETE =====\n'
|
||||
printf 'Report generation completed\n'
|
||||
printf 'Files created or updated:\n'
|
||||
local file
|
||||
for file in "${GENERATED_FILES[@]}"; do
|
||||
printf -- '- %s\n' "$file"
|
||||
done
|
||||
printf 'Customer code status:\n'
|
||||
printf '%s\n' "$customer_status"
|
||||
|
||||
if [[ -f "$ERROR_REPORT" ]]; then
|
||||
printf '\nValidation summary excerpt:\n'
|
||||
sed -n '1,8p' "$ERROR_REPORT"
|
||||
fi
|
||||
|
||||
if [[ -f "$SUMMARY_REPORT" ]]; then
|
||||
printf '\nAutomation summary excerpt:\n'
|
||||
sed -n '1,20p' "$SUMMARY_REPORT"
|
||||
fi
|
||||
}
|
||||
|
||||
main "$@"
|
||||
372
tools/validate-code.sh
Normal file
372
tools/validate-code.sh
Normal file
|
|
@ -0,0 +1,372 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -u -o pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
REPORT_DIR="$ROOT_DIR/reports"
|
||||
ERROR_REPORT="$REPORT_DIR/errors.md"
|
||||
GENERATED_REPORT="$REPORT_DIR/generated-files.md"
|
||||
STATUS_FILE="$REPORT_DIR/customer-code-status.txt"
|
||||
TMP_DIR=""
|
||||
|
||||
FILES_CHECKED=0
|
||||
WARNINGS=0
|
||||
ERRORS=0
|
||||
|
||||
DETAILS=()
|
||||
GENERATED_FILES=()
|
||||
|
||||
EXCLUDES=(
|
||||
".git"
|
||||
".forgejo"
|
||||
"node_modules"
|
||||
"vendor"
|
||||
"Library"
|
||||
"Temp"
|
||||
"Logs"
|
||||
"obj"
|
||||
"bin"
|
||||
)
|
||||
|
||||
cleanup() {
|
||||
if [[ -n "$TMP_DIR" && -d "$TMP_DIR" ]]; then
|
||||
rm -rf "$TMP_DIR"
|
||||
fi
|
||||
}
|
||||
|
||||
trap cleanup EXIT
|
||||
|
||||
record_generated_file() {
|
||||
local file="$1"
|
||||
local rel="${file#"$ROOT_DIR"/}"
|
||||
local existing
|
||||
|
||||
for existing in "${GENERATED_FILES[@]}"; do
|
||||
if [[ "$existing" == "$rel" ]]; then
|
||||
return
|
||||
fi
|
||||
done
|
||||
|
||||
GENERATED_FILES+=("$rel")
|
||||
}
|
||||
|
||||
escape_md() {
|
||||
printf '%s' "$1" | sed 's/|/\\|/g'
|
||||
}
|
||||
|
||||
have_tool() {
|
||||
command -v "$1" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
ensure_report_dir() {
|
||||
mkdir -p "$REPORT_DIR" || {
|
||||
printf 'Failed to create report directory: %s\n' "$REPORT_DIR" >&2
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
add_warning() {
|
||||
local message="$1"
|
||||
WARNINGS=$((WARNINGS + 1))
|
||||
DETAILS+=("- Warning: $(escape_md "$message")")
|
||||
}
|
||||
|
||||
add_error() {
|
||||
local message="$1"
|
||||
ERRORS=$((ERRORS + 1))
|
||||
DETAILS+=("- Error: $(escape_md "$message")")
|
||||
}
|
||||
|
||||
add_ok() {
|
||||
local message="$1"
|
||||
DETAILS+=("- OK: $(escape_md "$message")")
|
||||
}
|
||||
|
||||
relative_path() {
|
||||
local path="$1"
|
||||
path="${path#"$ROOT_DIR"/}"
|
||||
printf '%s' "$path"
|
||||
}
|
||||
|
||||
join_by() {
|
||||
local delimiter="$1"
|
||||
shift || true
|
||||
local first=1
|
||||
local item
|
||||
|
||||
for item in "$@"; do
|
||||
[[ -z "$item" ]] && continue
|
||||
|
||||
if [[ $first -eq 1 ]]; then
|
||||
printf '%s' "$item"
|
||||
first=0
|
||||
else
|
||||
printf '%s%s' "$delimiter" "$item"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
collect_files() {
|
||||
local find_args=("$ROOT_DIR")
|
||||
local exclude
|
||||
|
||||
if ((${#EXCLUDES[@]} > 0)); then
|
||||
find_args+=("(")
|
||||
for exclude in "${EXCLUDES[@]}"; do
|
||||
find_args+=(-name "$exclude" -o)
|
||||
done
|
||||
unset 'find_args[${#find_args[@]}-1]'
|
||||
find_args+=(")" -prune -o)
|
||||
fi
|
||||
|
||||
find_args+=(-type f -print0)
|
||||
find "${find_args[@]}"
|
||||
}
|
||||
|
||||
run_check() {
|
||||
local label="$1"
|
||||
local file="$2"
|
||||
shift 2
|
||||
|
||||
local stdout_file="$TMP_DIR/stdout.log"
|
||||
local stderr_file="$TMP_DIR/stderr.log"
|
||||
local stdout=""
|
||||
local stderr=""
|
||||
local combined=""
|
||||
|
||||
FILES_CHECKED=$((FILES_CHECKED + 1))
|
||||
|
||||
: >"$stdout_file"
|
||||
: >"$stderr_file"
|
||||
|
||||
if "$@" >"$stdout_file" 2>"$stderr_file"; then
|
||||
add_ok "$label passed: $(relative_path "$file")"
|
||||
else
|
||||
stdout="$(tr '\n' ' ' <"$stdout_file" | sed 's/[[:space:]]\+/ /g; s/^ //; s/ $//')"
|
||||
stderr="$(tr '\n' ' ' <"$stderr_file" | sed 's/[[:space:]]\+/ /g; s/^ //; s/ $//')"
|
||||
combined="$(join_by ' | ' "${stdout:-}" "${stderr:-}")"
|
||||
add_error "$label failed: $(relative_path "$file")${combined:+ | $combined}"
|
||||
fi
|
||||
}
|
||||
|
||||
run_repo_build_checks() {
|
||||
local sln_files=()
|
||||
local csproj_files=()
|
||||
local cmake_file="$ROOT_DIR/CMakeLists.txt"
|
||||
local tmp_cmake_dir=""
|
||||
|
||||
while IFS= read -r -d '' file; do
|
||||
sln_files+=("$file")
|
||||
done < <(find "$ROOT_DIR" -path "$ROOT_DIR/.git" -prune -o -path "$ROOT_DIR/.forgejo" -prune -o -name '*.sln' -type f -print0)
|
||||
|
||||
while IFS= read -r -d '' file; do
|
||||
csproj_files+=("$file")
|
||||
done < <(find "$ROOT_DIR" -path "$ROOT_DIR/.git" -prune -o -path "$ROOT_DIR/.forgejo" -prune -o -name '*.csproj' -type f -print0)
|
||||
|
||||
if ((${#sln_files[@]} > 0 || ${#csproj_files[@]} > 0)); then
|
||||
if have_tool dotnet; then
|
||||
if ((${#sln_files[@]} > 0)); then
|
||||
local file
|
||||
for file in "${sln_files[@]}"; do
|
||||
run_check "dotnet build" "$file" dotnet build "$file" --nologo --verbosity minimal
|
||||
done
|
||||
else
|
||||
local file
|
||||
for file in "${csproj_files[@]}"; do
|
||||
run_check "dotnet build" "$file" dotnet build "$file" --nologo --verbosity minimal
|
||||
done
|
||||
fi
|
||||
else
|
||||
add_warning "dotnet not installed; skipping C# project validation."
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -f "$cmake_file" ]]; then
|
||||
if have_tool cmake; then
|
||||
FILES_CHECKED=$((FILES_CHECKED + 1))
|
||||
tmp_cmake_dir="$(mktemp -d)"
|
||||
if cmake -S "$ROOT_DIR" -B "$tmp_cmake_dir" >"$TMP_DIR/stdout.log" 2>"$TMP_DIR/stderr.log"; then
|
||||
add_ok "cmake configure passed: CMakeLists.txt"
|
||||
else
|
||||
local stdout stderr combined
|
||||
stdout="$(tr '\n' ' ' <"$TMP_DIR/stdout.log" | sed 's/[[:space:]]\+/ /g; s/^ //; s/ $//')"
|
||||
stderr="$(tr '\n' ' ' <"$TMP_DIR/stderr.log" | sed 's/[[:space:]]\+/ /g; s/^ //; s/ $//')"
|
||||
combined="$(join_by ' | ' "${stdout:-}" "${stderr:-}")"
|
||||
add_error "cmake configure failed: CMakeLists.txt${combined:+ | $combined}"
|
||||
fi
|
||||
rm -rf "$tmp_cmake_dir"
|
||||
else
|
||||
add_warning "cmake not installed; skipping C/C++ validation."
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
validate_file() {
|
||||
local file="$1"
|
||||
local base
|
||||
|
||||
base="$(basename "$file")"
|
||||
|
||||
case "$file" in
|
||||
*.php)
|
||||
if have_tool php; then
|
||||
run_check "php -l" "$file" php -l "$file"
|
||||
else
|
||||
add_warning "php not installed; skipping $(relative_path "$file")"
|
||||
fi
|
||||
;;
|
||||
*.py)
|
||||
if have_tool python3; then
|
||||
run_check "python3 -m py_compile" "$file" python3 -m py_compile "$file"
|
||||
else
|
||||
add_warning "python3 not installed; skipping $(relative_path "$file")"
|
||||
fi
|
||||
;;
|
||||
*.rb)
|
||||
if have_tool ruby; then
|
||||
run_check "ruby -c" "$file" ruby -c "$file"
|
||||
else
|
||||
add_warning "ruby not installed; skipping $(relative_path "$file")"
|
||||
fi
|
||||
;;
|
||||
*.pl|*.pm)
|
||||
if have_tool perl; then
|
||||
run_check "perl -c" "$file" perl -c "$file"
|
||||
else
|
||||
add_warning "perl not installed; skipping $(relative_path "$file")"
|
||||
fi
|
||||
;;
|
||||
*.sh|*.bash)
|
||||
if have_tool bash; then
|
||||
run_check "bash -n" "$file" bash -n "$file"
|
||||
else
|
||||
add_warning "bash not installed; skipping $(relative_path "$file")"
|
||||
fi
|
||||
;;
|
||||
*.js|*.mjs|*.cjs)
|
||||
if have_tool node; then
|
||||
run_check "node --check" "$file" node --check "$file"
|
||||
else
|
||||
add_warning "node not installed; skipping $(relative_path "$file")"
|
||||
fi
|
||||
;;
|
||||
*.json)
|
||||
if have_tool jq; then
|
||||
run_check "jq validation" "$file" jq empty "$file"
|
||||
else
|
||||
add_warning "jq not installed; skipping $(relative_path "$file")"
|
||||
fi
|
||||
;;
|
||||
*.yml|*.yaml)
|
||||
if have_tool yamllint; then
|
||||
run_check "yamllint" "$file" yamllint -f parsable "$file"
|
||||
else
|
||||
add_warning "yamllint not installed; skipping $(relative_path "$file")"
|
||||
fi
|
||||
;;
|
||||
*.html|*.htm)
|
||||
if have_tool tidy; then
|
||||
run_check "tidy validation" "$file" tidy -qe "$file"
|
||||
else
|
||||
add_warning "tidy not installed; skipping $(relative_path "$file")"
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
if [[ "$base" == "Dockerfile" ]]; then
|
||||
FILES_CHECKED=$((FILES_CHECKED + 1))
|
||||
add_ok "Detected Dockerfile: $(relative_path "$file")"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
write_generated_files_report() {
|
||||
{
|
||||
printf '# Generated Files\n\n'
|
||||
if ((${#GENERATED_FILES[@]} == 0)); then
|
||||
printf 'No report files were generated.\n'
|
||||
else
|
||||
local file
|
||||
for file in "${GENERATED_FILES[@]}"; do
|
||||
printf -- '- %s\n' "$file"
|
||||
done
|
||||
fi
|
||||
} >"$GENERATED_REPORT" || return 1
|
||||
|
||||
record_generated_file "$GENERATED_REPORT"
|
||||
}
|
||||
|
||||
write_status_file() {
|
||||
local status_line="NO CUSTOMER CODE ERRORS FOUND"
|
||||
if ((ERRORS > 0)); then
|
||||
status_line="CUSTOMER CODE ERRORS FOUND"
|
||||
fi
|
||||
|
||||
printf '%s\n' "$status_line" >"$STATUS_FILE" || return 1
|
||||
record_generated_file "$STATUS_FILE"
|
||||
}
|
||||
|
||||
write_error_report() {
|
||||
local status_line="NO CUSTOMER CODE ERRORS FOUND"
|
||||
if ((ERRORS > 0)); then
|
||||
status_line="CUSTOMER CODE ERRORS FOUND"
|
||||
fi
|
||||
|
||||
{
|
||||
printf '# Validation Summary\n\n'
|
||||
printf '%s\n\n' "$status_line"
|
||||
printf -- '- Files Checked: %s\n' "$FILES_CHECKED"
|
||||
printf -- '- Warnings: %s\n' "$WARNINGS"
|
||||
printf -- '- Errors: %s\n\n' "$ERRORS"
|
||||
printf '## Detailed Results\n\n'
|
||||
if ((${#DETAILS[@]} == 0)); then
|
||||
printf 'No validation steps were run.\n'
|
||||
else
|
||||
printf '%s\n' "${DETAILS[@]}"
|
||||
fi
|
||||
} >"$ERROR_REPORT" || return 1
|
||||
|
||||
record_generated_file "$ERROR_REPORT"
|
||||
}
|
||||
|
||||
print_terminal_summary() {
|
||||
local status_line="NO CUSTOMER CODE ERRORS FOUND"
|
||||
if ((ERRORS > 0)); then
|
||||
status_line="CUSTOMER CODE ERRORS FOUND"
|
||||
fi
|
||||
|
||||
printf '===== VALIDATION SUMMARY =====\n'
|
||||
printf '%s\n' "$status_line"
|
||||
printf 'Files checked: %s\n' "$FILES_CHECKED"
|
||||
printf 'Warnings: %s\n' "$WARNINGS"
|
||||
printf 'Errors: %s\n' "$ERRORS"
|
||||
printf 'Reports written:\n'
|
||||
|
||||
local file
|
||||
for file in "${GENERATED_FILES[@]}"; do
|
||||
printf -- '- %s\n' "$file"
|
||||
done
|
||||
}
|
||||
|
||||
main() {
|
||||
ensure_report_dir || return 1
|
||||
TMP_DIR="$(mktemp -d)" || {
|
||||
printf 'Failed to create temporary directory.\n' >&2
|
||||
return 1
|
||||
}
|
||||
|
||||
while IFS= read -r -d '' file; do
|
||||
validate_file "$file"
|
||||
done < <(collect_files)
|
||||
|
||||
run_repo_build_checks
|
||||
|
||||
write_status_file || return 1
|
||||
write_error_report || return 1
|
||||
write_generated_files_report || return 1
|
||||
print_terminal_summary
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Loading…
Add table
Add a link
Reference in a new issue