generate-docs.sh 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. #!/usr/bin/env bash
  2. set -euo pipefail
  3. declare -A VISITED
  4. TREE_TEMP=$(mktemp)
  5. BODY_TEMP=$(mktemp)
  6. trap 'rm -f "$TREE_TEMP" "$BODY_TEMP"' EXIT
  7. # ----------------------------
  8. # Helpers
  9. # ----------------------------
  10. strip_colors() {
  11. sed -E "s/\x1B\[[0-9;]*[mK]//g"
  12. }
  13. run_help() {
  14. local project="./RackPeek"
  15. local config="Release"
  16. local publish_dir="$project/publish"
  17. local exe="$publish_dir/RackPeek"
  18. if [[ ! -x "$exe" ]]; then
  19. echo "Publishing RackPeek ($config)..." >&2
  20. dotnet publish "$project" -c "$config" -o "$publish_dir" --self-contained false -p:PublishSingleFile=true >&2
  21. fi
  22. local output
  23. output=$("$exe" "$@" 2>&1 | strip_colors)
  24. echo "$output"
  25. }
  26. # UPDATED: Handles descriptions that start on the next line
  27. get_description() {
  28. local help_output="$1"
  29. echo "$help_output" | awk '
  30. # If we find the header...
  31. /^DESCRIPTION:/ {
  32. in_desc = 1
  33. # Remove the header itself
  34. sub(/^DESCRIPTION:[[:space:]]*/, "")
  35. # If there is text on this same line, print it and quit
  36. if (length($0) > 0) {
  37. print $0
  38. exit
  39. }
  40. next
  41. }
  42. # If we are inside the description block...
  43. in_desc {
  44. # Stop if we hit an empty line or the next Section (UPPERCASE:)
  45. if (/^[[:space:]]*$/) exit
  46. if (/^[A-Z]+:/) exit
  47. # Trim leading/trailing whitespace
  48. gsub(/^[[:space:]]+|[[:space:]]+$/, "")
  49. # Print the line (we only take the first non-empty line for the index)
  50. print $0
  51. exit
  52. }
  53. '
  54. }
  55. # UPDATED: Handles "add <name>" correctly by taking only the first column ($1)
  56. get_child_commands() {
  57. local help_output="$1"
  58. echo "$help_output" | awk '
  59. BEGIN { in_commands = 0 }
  60. /^COMMANDS:/ { in_commands = 1; next }
  61. in_commands {
  62. # Stop if we hit an empty line or a new section
  63. if ($0 ~ /^[[:space:]]*$/) exit;
  64. if ($0 ~ /^[A-Z]+:/) exit;
  65. # Match lines that look like commands (indented)
  66. if ($0 ~ /^[[:space:]]+[a-zA-Z0-9-]+/) {
  67. # Print only the first word (the command name)
  68. print $1
  69. }
  70. }
  71. '
  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. for child in "${child_cmds[@]}"; do
  128. echo "Recursing into: ${display_header} ${child}" >&2
  129. local next_path
  130. if [[ -z "$path_string" ]]; then
  131. next_path="$child"
  132. else
  133. next_path="$path_string $child"
  134. fi
  135. generate_help_recursive "$next_path"
  136. done
  137. }
  138. is_invalid_help() {
  139. local output="$1"
  140. # Detect common failure patterns
  141. if echo "$output" | grep -qiE \
  142. "Unknown command|CommandParseException|Unexpected error occurred"; then
  143. return 0
  144. fi
  145. return 1
  146. }
  147. # ----------------------------
  148. # Main
  149. # ----------------------------
  150. echo "Generating documentation..."
  151. generate_help_recursive ""
  152. {
  153. echo ""
  154. cat "$TREE_TEMP"
  155. } > "Shared.Rcl/wwwroot/raw_docs/CommandIndex.md"
  156. {
  157. echo "# CLI Commands"
  158. echo ""
  159. cat "$BODY_TEMP"
  160. } > "Shared.Rcl/wwwroot/raw_docs/Commands.md"
  161. echo "Generated Successfully."