generate_commands_markdown.sh 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  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 -r "s/\x1B\[[0-9;]*[mK]//g"
  14. }
  15. run_help() {
  16. local project="./RackPeek"
  17. local runtime="osx-arm64"
  18. local config="Release"
  19. local publish_dir="$project/bin/$config/net10.0/$runtime/publish"
  20. local exe="$publish_dir/RackPeek"
  21. if [[ ! -x "$exe" ]]; then
  22. echo "Publishing RackPeek ($config)..." >&2
  23. dotnet publish "$project" -c "$config" -r "$runtime" --self-contained false -p:PublishSingleFile=true >&2
  24. fi
  25. local output
  26. output=$("$exe" "$@" 2>&1 | strip_colors)
  27. echo "$output"
  28. }
  29. # UPDATED: Handles descriptions that start on the next line
  30. get_description() {
  31. local help_output="$1"
  32. echo "$help_output" | awk '
  33. # If we find the header...
  34. /^DESCRIPTION:/ {
  35. in_desc = 1
  36. # Remove the header itself
  37. sub(/^DESCRIPTION:[[:space:]]*/, "")
  38. # If there is text on this same line, print it and quit
  39. if (length($0) > 0) {
  40. print $0
  41. exit
  42. }
  43. next
  44. }
  45. # If we are inside the description block...
  46. in_desc {
  47. # Stop if we hit an empty line or the next Section (UPPERCASE:)
  48. if (/^[[:space:]]*$/) exit
  49. if (/^[A-Z]+:/) exit
  50. # Trim leading/trailing whitespace
  51. gsub(/^[[:space:]]+|[[:space:]]+$/, "")
  52. # Print the line (we only take the first non-empty line for the index)
  53. print $0
  54. exit
  55. }
  56. '
  57. }
  58. # UPDATED: Handles "add <name>" correctly by taking only the first column ($1)
  59. get_child_commands() {
  60. local help_output="$1"
  61. echo "$help_output" | awk '
  62. BEGIN { in_commands = 0 }
  63. /^COMMANDS:/ { in_commands = 1; next }
  64. in_commands {
  65. # Stop if we hit an empty line or a new section
  66. if ($0 ~ /^[[:space:]]*$/) exit;
  67. if ($0 ~ /^[A-Z]+:/) exit;
  68. # Match lines that look like commands (indented)
  69. if ($0 ~ /^[[:space:]]+[a-zA-Z0-9-]+/) {
  70. # Print only the first word (the command name)
  71. print $1
  72. }
  73. }
  74. '
  75. }
  76. # ----------------------------
  77. # Recursion
  78. # ----------------------------
  79. generate_help_recursive() {
  80. local path_string="$1"
  81. # Convert string to array for the run_help command
  82. local current_cmd_array=()
  83. if [[ -n "$path_string" ]]; then
  84. IFS=' ' read -r -a current_cmd_array <<< "$path_string"
  85. fi
  86. local map_key="${path_string:-root}"
  87. if [[ -n "${VISITED["$map_key"]:-}" ]]; then return; fi
  88. VISITED["$map_key"]=1
  89. # 1. Run Help
  90. local help_output
  91. if ! help_output=$(run_help "${current_cmd_array[@]}" --help); then
  92. echo "Skipping: $map_key (help failed)" >&2
  93. return
  94. fi
  95. # 2. Extract Description
  96. local description
  97. description=$(get_description "$help_output")
  98. # 3. Build Tree Entry
  99. local depth=${#current_cmd_array[@]}
  100. local indent=""
  101. [[ $depth -gt 0 ]] && printf -v indent "%*s" $((depth * 2)) ""
  102. local tree_label
  103. [[ $depth -eq 0 ]] && tree_label="rpk" || tree_label="${current_cmd_array[-1]}"
  104. local anchor_text="rpk $path_string"
  105. anchor_text="${anchor_text% }"
  106. local anchor_link=$(echo "$anchor_text" | tr '[:upper:]' '[:lower:]' | tr ' ' '-')
  107. # Format: - [label](link) - Description
  108. local tree_entry="${indent}- [${tree_label}](Commands.md#${anchor_link})"
  109. if [[ -n "$description" ]]; then
  110. tree_entry="${tree_entry} - ${description}"
  111. fi
  112. echo "$tree_entry" >> "$TREE_TEMP"
  113. # 4. Write Body
  114. local display_header
  115. [[ -z "$path_string" ]] && display_header="rpk" || display_header="rpk $path_string"
  116. {
  117. echo "## \`${display_header}\`"
  118. echo '```'
  119. echo "$help_output"
  120. echo '```'
  121. echo ""
  122. } >> "$BODY_TEMP"
  123. # 5. Recurse
  124. local child_cmds
  125. mapfile -t child_cmds < <(get_child_commands "$help_output")
  126. for child in "${child_cmds[@]}"; do
  127. echo "Recursing into: ${display_header} ${child}" >&2
  128. local next_path
  129. if [[ -z "$path_string" ]]; then
  130. next_path="$child"
  131. else
  132. next_path="$path_string $child"
  133. fi
  134. generate_help_recursive "$next_path"
  135. done
  136. }
  137. # ----------------------------
  138. # Main
  139. # ----------------------------
  140. echo "Generating documentation..."
  141. generate_help_recursive ""
  142. {
  143. echo ""
  144. cat "$TREE_TEMP"
  145. } > "CommandIndex.md"
  146. {
  147. echo "# CLI Commands"
  148. echo ""
  149. cat "$BODY_TEMP"
  150. } > "Commands.md"
  151. echo "Generated Successfully."