output.sh 2.7 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192
  1. # net-ops :: _lib/output.sh
  2. # Output mode handling for probe scripts. Supports two modes:
  3. # - text (default): human-readable [PASS]/[FAIL] lines + summary block
  4. # - json: newline-delimited JSON, one record per check + a summary record
  5. #
  6. # Usage in a probe script:
  7. # source "$(dirname "$0")/../_lib/output.sh"
  8. # parse_output_flags "$@"
  9. # # then use section / pass / fail as before — they auto-route to the right mode
  10. JSON_MODE="${JSON_MODE:-0}"
  11. parse_output_flags() {
  12. for a in "$@"; do
  13. [[ "$a" == "--json" ]] && JSON_MODE=1
  14. done
  15. }
  16. # JSON-safe string escaper. Handles backslash, double-quote, and control chars.
  17. _json_escape() {
  18. local s="$1"
  19. s="${s//\\/\\\\}"
  20. s="${s//\"/\\\"}"
  21. s="${s//$'\n'/\\n}"
  22. s="${s//$'\r'/\\r}"
  23. s="${s//$'\t'/\\t}"
  24. printf '%s' "$s"
  25. }
  26. # These three are the public API. They write either text or JSON depending on mode.
  27. PASS_COUNT=0
  28. FAIL_COUNT=0
  29. FIRST_FAIL=""
  30. CURRENT_SECTION=""
  31. section() {
  32. CURRENT_SECTION="$1"
  33. if [[ "$JSON_MODE" -eq 1 ]]; then
  34. printf '{"type":"section","name":"%s"}\n' "$(_json_escape "$1")"
  35. else
  36. echo
  37. echo "=== $1 ==="
  38. fi
  39. }
  40. pass() {
  41. PASS_COUNT=$((PASS_COUNT + 1))
  42. if [[ "$JSON_MODE" -eq 1 ]]; then
  43. printf '{"type":"check","section":"%s","label":"%s","status":"pass","detail":"%s"}\n' \
  44. "$(_json_escape "$CURRENT_SECTION")" "$(_json_escape "$1")" "$(_json_escape "${2:-}")"
  45. else
  46. echo "[PASS] $1${2:+ :: $2}"
  47. fi
  48. }
  49. fail() {
  50. FAIL_COUNT=$((FAIL_COUNT + 1))
  51. [[ -z "$FIRST_FAIL" ]] && FIRST_FAIL="[$CURRENT_SECTION] $1"
  52. if [[ "$JSON_MODE" -eq 1 ]]; then
  53. printf '{"type":"check","section":"%s","label":"%s","status":"fail","detail":"%s"}\n' \
  54. "$(_json_escape "$CURRENT_SECTION")" "$(_json_escape "$1")" "$(_json_escape "${2:-}")"
  55. else
  56. echo "[FAIL] $1${2:+ :: $2}"
  57. fi
  58. }
  59. # Call from end of probe to emit summary record / block.
  60. emit_summary() {
  61. if [[ "$JSON_MODE" -eq 1 ]]; then
  62. printf '{"type":"summary","pass":%d,"fail":%d,"first_fail":"%s"}\n' \
  63. "$PASS_COUNT" "$FAIL_COUNT" "$(_json_escape "$FIRST_FAIL")"
  64. else
  65. echo
  66. echo "=== SUMMARY ==="
  67. echo " PASS: $PASS_COUNT FAIL: $FAIL_COUNT"
  68. if [[ -n "$FIRST_FAIL" ]]; then
  69. echo " First failure: $FIRST_FAIL"
  70. else
  71. echo " No failures."
  72. fi
  73. fi
  74. }
  75. # Helper for scripts that want to suppress informational/diagnostic output
  76. # (the non-PASS/FAIL annotations like scutil dumps) in JSON mode.
  77. info() {
  78. if [[ "$JSON_MODE" -eq 1 ]]; then
  79. # Optional: emit info records. Keep silent for cleaner JSON parsing.
  80. return 0
  81. fi
  82. echo "$@"
  83. }