generate_commands_markdown.sh 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  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. # 2. Extract Description
  95. local description
  96. description=$(get_description "$help_output")
  97. # 3. Build Tree Entry
  98. local depth=${#current_cmd_array[@]}
  99. local indent=""
  100. [[ $depth -gt 0 ]] && printf -v indent "%*s" $((depth * 2)) ""
  101. local tree_label
  102. [[ $depth -eq 0 ]] && tree_label="rpk" || tree_label="${current_cmd_array[-1]}"
  103. local anchor_text="rpk $path_string"
  104. anchor_text="${anchor_text% }"
  105. local anchor_link=$(echo "$anchor_text" | tr '[:upper:]' '[:lower:]' | tr ' ' '-')
  106. # Format: - [label](link) - Description
  107. local tree_entry="${indent}- [${tree_label}](Commands.md#${anchor_link})"
  108. if [[ -n "$description" ]]; then
  109. tree_entry="${tree_entry} - ${description}"
  110. fi
  111. echo "$tree_entry" >> "$TREE_TEMP"
  112. # 4. Write Body
  113. local display_header
  114. [[ -z "$path_string" ]] && display_header="rpk" || display_header="rpk $path_string"
  115. {
  116. echo "## \`${display_header}\`"
  117. echo '```'
  118. echo "$help_output"
  119. echo '```'
  120. echo ""
  121. } >> "$BODY_TEMP"
  122. # 5. Recurse
  123. local child_cmds
  124. mapfile -t child_cmds < <(get_child_commands "$help_output")
  125. for child in "${child_cmds[@]}"; do
  126. echo "Recursing into: ${display_header} ${child}" >&2
  127. local next_path
  128. if [[ -z "$path_string" ]]; then
  129. next_path="$child"
  130. else
  131. next_path="$path_string $child"
  132. fi
  133. generate_help_recursive "$next_path"
  134. done
  135. }
  136. # ----------------------------
  137. # Main
  138. # ----------------------------
  139. echo "Generating documentation..."
  140. generate_help_recursive ""
  141. {
  142. echo ""
  143. cat "$TREE_TEMP"
  144. } > "CommandIndex.md"
  145. {
  146. echo "# CLI Commands"
  147. echo ""
  148. cat "$BODY_TEMP"
  149. } > "Commands.md"
  150. echo "Generated Successfully."