Unity-Customer-Template/tools/generate-code-docs.sh
Frank Harris 28c15035a4
All checks were successful
Code Documentation / code-docs (push) Successful in 6s
Project Report / project-report (push) Successful in 4s
Validate Code / validate (push) Successful in 3s
added unity files
2026-06-15 12:03:33 -05:00

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 "$@"