generate-docs.sh 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. #!/usr/bin/env bash
  2. set -euo pipefail
  3. PROJECT_PATH="."
  4. OUTPUT_FILE="COMMANDS.md"
  5. declare -A VISITED
  6. TREE_TEMP=$(mktemp)
  7. BODY_TEMP=$(mktemp)
  8. trap 'rm -f "$TREE_TEMP" "$BODY_TEMP"' EXIT
  9. # ----------------------------
  10. # Helpers
  11. # ----------------------------
  12. strip_colors() {
  13. sed -E "s/\x1B\[[0-9;]*[mK]//g"
  14. }
  15. run_help() {
  16. local project="./RackPeek"
  17. local config="Release"
  18. local publish_dir="$project/publish"
  19. local exe="$publish_dir/RackPeek"
  20. if [[ ! -x "$exe" ]]; then
  21. echo "Publishing RackPeek ($config)..." >&2
  22. dotnet publish "$project" -c "$config" -o "$publish_dir" --self-contained false -p:PublishSingleFile=true >&2
  23. fi
  24. local output
  25. output=$("$exe" "$@" 2>&1 | strip_colors || true)
  26. echo "$output"
  27. }
  28. # UPDATED: Handles descriptions that start on the next line
  29. get_description() {
  30. local help_output="$1"
  31. echo "$help_output" | awk '
  32. # If we find the header...
  33. /^DESCRIPTION:/ {
  34. in_desc = 1
  35. # Remove the header itself
  36. sub(/^DESCRIPTION:[[:space:]]*/, "")
  37. # If there is text on this same line, print it and quit
  38. if (length($0) > 0) {
  39. print $0
  40. exit
  41. }
  42. next
  43. }
  44. # If we are inside the description block...
  45. in_desc {
  46. # Stop if we hit an empty line or the next Section (UPPERCASE:)
  47. if (/^[[:space:]]*$/) exit
  48. if (/^[A-Z]+:/) exit
  49. # Trim leading/trailing whitespace
  50. gsub(/^[[:space:]]+|[[:space:]]+$/, "")
  51. # Print the line (we only take the first non-empty line for the index)
  52. print $0
  53. exit
  54. }
  55. '
  56. }
  57. get_child_commands() {
  58. local help_output="$1"
  59. echo "$help_output" | awk '
  60. /^COMMANDS:/ { in_commands=1; next }
  61. in_commands {
  62. # stop when another section header appears
  63. if ($0 ~ /^[A-Z]+:/) exit
  64. # ignore empty lines
  65. if ($0 ~ /^[[:space:]]*$/) next
  66. # capture first token (command name)
  67. if (match($0, /^[[:space:]]*([a-zA-Z0-9_-]+)/, m)) {
  68. print m[1]
  69. }
  70. }
  71. ' | sort -u
  72. }
  73. # ----------------------------
  74. # Recursion
  75. # ----------------------------
  76. generate_help_recursive() {
  77. local path_string="$1"
  78. # Convert string to array for the run_help command
  79. local current_cmd_array=()
  80. if [[ -n "$path_string" ]]; then
  81. IFS=' ' read -r -a current_cmd_array <<< "$path_string"
  82. fi
  83. local map_key="${path_string:-root}"
  84. if [[ -n "${VISITED["$map_key"]:-}" ]]; then return; fi
  85. VISITED["$map_key"]=1
  86. # 1. Run Help
  87. local help_output
  88. if ! help_output=$(run_help "${current_cmd_array[@]}" --help); then
  89. echo "Skipping: $map_key (help failed)" >&2
  90. return
  91. fi
  92. if is_invalid_help "$help_output"; then
  93. echo "Skipping: $map_key (invalid command)" >&2
  94. return
  95. fi
  96. # 2. Extract Description
  97. local description
  98. description=$(get_description "$help_output")
  99. # 3. Build Tree Entry
  100. local depth=${#current_cmd_array[@]}
  101. local indent=""
  102. [[ $depth -gt 0 ]] && printf -v indent "%*s" $((depth * 2)) ""
  103. local tree_label
  104. [[ $depth -eq 0 ]] && tree_label="rpk" || tree_label="${current_cmd_array[-1]}"
  105. local anchor_text="rpk $path_string"
  106. anchor_text="${anchor_text% }"
  107. local anchor_link=$(echo "$anchor_text" | tr '[:upper:]' '[:lower:]' | tr ' ' '-')
  108. # Format: - [label](link) - Description
  109. local tree_entry="${indent}- [${tree_label}](docs/Commands.md#${anchor_link})"
  110. if [[ -n "$description" ]]; then
  111. tree_entry="${tree_entry} - ${description}"
  112. fi
  113. echo "$tree_entry" >> "$TREE_TEMP"
  114. # 4. Write Body
  115. local display_header
  116. [[ -z "$path_string" ]] && display_header="rpk" || display_header="rpk $path_string"
  117. {
  118. echo "## \`${display_header}\`"
  119. echo '```'
  120. echo "$help_output"
  121. echo '```'
  122. echo ""
  123. } >> "$BODY_TEMP"
  124. # 5. Recurse
  125. local child_cmds
  126. mapfile -t child_cmds < <(get_child_commands "$help_output")
  127. # If no children, still create a leaf entry
  128. if [[ ${#child_cmds[@]} -eq 0 && ${#current_cmd_array[@]} -gt 0 ]]; then
  129. # Leaf command entry
  130. echo "$tree_entry" >> "$TREE_TEMP"
  131. fi
  132. for child in "${child_cmds[@]}"; do
  133. echo "Recursing into: ${display_header} ${child}" >&2
  134. local next_path
  135. if [[ -z "$path_string" ]]; then
  136. next_path="$child"
  137. else
  138. next_path="$path_string $child"
  139. fi
  140. generate_help_recursive "$next_path"
  141. done
  142. }
  143. is_invalid_help() {
  144. local output="$1"
  145. # Detect common failure patterns
  146. if echo "$output" | grep -qiE \
  147. "Unknown command|CommandParseException|Unexpected error occurred"; then
  148. return 0
  149. fi
  150. return 1
  151. }
  152. # ----------------------------
  153. # Main
  154. # ----------------------------
  155. echo "Generating documentation..."
  156. generate_help_recursive ""
  157. {
  158. echo ""
  159. cat "$TREE_TEMP"
  160. } > "Shared.Rcl/wwwroot/raw_docs/CommandIndex.md"
  161. {
  162. echo "# CLI Commands"
  163. echo ""
  164. cat "$BODY_TEMP"
  165. } > "Shared.Rcl/wwwroot/raw_docs/Commands.md"
  166. echo "Generated Successfully."