generated from DevPlus1/Unity-Customer-Template
675 lines
24 KiB
Bash
675 lines
24 KiB
Bash
#!/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 "$@"
|