generate-docs.sh 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  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)
  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. # UPDATED: Handles "add <name>" correctly by taking only the first column ($1)
  58. get_child_commands() {
  59. local help_output="$1"
  60. echo "$help_output" | awk '
  61. BEGIN { in_commands = 0 }
  62. /^COMMANDS:/ { in_commands = 1; next }
  63. in_commands {
  64. # Stop if we hit an empty line or a new section
  65. if ($0 ~ /^[[:space:]]*$/) exit;
  66. if ($0 ~ /^[A-Z]+:/) exit;
  67. # Match lines that look like commands (indented)
  68. if ($0 ~ /^[[:space:]]+[a-zA-Z0-9-]+/) {
  69. # Print only the first word (the command name)
  70. print $1
  71. }
  72. }
  73. '
  74. }
  75. # ----------------------------
  76. # Recursion
  77. # ----------------------------
  78. generate_help_recursive() {
  79. local path_string="$1"
  80. # Convert string to array for the run_help command
  81. local current_cmd_array=()
  82. if [[ -n "$path_string" ]]; then
  83. IFS=' ' read -r -a current_cmd_array <<< "$path_string"
  84. fi
  85. local map_key="${path_string:-root}"
  86. if [[ -n "${VISITED["$map_key"]:-}" ]]; then return; fi
  87. VISITED["$map_key"]=1
  88. # 1. Run Help
  89. local help_output
  90. if ! help_output=$(run_help "${current_cmd_array[@]}" --help); then
  91. echo "Skipping: $map_key (help failed)" >&2
  92. return
  93. fi
  94. if is_invalid_help "$help_output"; then
  95. echo "Skipping: $map_key (invalid command)" >&2
  96. return
  97. fi
  98. # 2. Extract Description
  99. local description
  100. description=$(get_description "$help_output")
  101. # 3. Build Tree Entry
  102. local depth=${#current_cmd_array[@]}
  103. local indent=""
  104. [[ $depth -gt 0 ]] && printf -v indent "%*s" $((depth * 2)) ""
  105. local tree_label
  106. [[ $depth -eq 0 ]] && tree_label="rpk" || tree_label="${current_cmd_array[-1]}"
  107. local anchor_text="rpk $path_string"
  108. anchor_text="${anchor_text% }"
  109. local anchor_link=$(echo "$anchor_text" | tr '[:upper:]' '[:lower:]' | tr ' ' '-')
  110. # Format: - [label](link) - Description
  111. local tree_entry="${indent}- [${tree_label}](docs/Commands.md#${anchor_link})"
  112. if [[ -n "$description" ]]; then
  113. tree_entry="${tree_entry} - ${description}"
  114. fi
  115. echo "$tree_entry" >> "$TREE_TEMP"
  116. # 4. Write Body
  117. local display_header
  118. [[ -z "$path_string" ]] && display_header="rpk" || display_header="rpk $path_string"
  119. {
  120. echo "## \`${display_header}\`"
  121. echo '```'
  122. echo "$help_output"
  123. echo '```'
  124. echo ""
  125. } >> "$BODY_TEMP"
  126. # 5. Recurse
  127. local child_cmds
  128. mapfile -t child_cmds < <(get_child_commands "$help_output")
  129. for child in "${child_cmds[@]}"; do
  130. echo "Recursing into: ${display_header} ${child}" >&2
  131. local next_path
  132. if [[ -z "$path_string" ]]; then
  133. next_path="$child"
  134. else
  135. next_path="$path_string $child"
  136. fi
  137. generate_help_recursive "$next_path"
  138. done
  139. }
  140. is_invalid_help() {
  141. local output="$1"
  142. # Detect common failure patterns
  143. if echo "$output" | grep -qiE \
  144. "Unknown command|CommandParseException|Unexpected error occurred"; then
  145. return 0
  146. fi
  147. return 1
  148. }
  149. # ----------------------------
  150. # Main
  151. # ----------------------------
  152. echo "Generating documentation..."
  153. generate_help_recursive ""
  154. {
  155. echo ""
  156. cat "$TREE_TEMP"
  157. } > "Shared.Rcl/wwwroot/raw_docs/CommandIndex.md"
  158. {
  159. echo "# CLI Commands"
  160. echo ""
  161. cat "$BODY_TEMP"
  162. } > "Shared.Rcl/wwwroot/raw_docs/Commands.md"
  163. echo "Generated Successfully."